Download as pdf or txt
Download as pdf or txt
You are on page 1of 384

Tytuł oryginału: HTML5 Developer's Cookbook

Tłumaczenie: Grzegorz Balcerek

ISBN: 978-83-246-4966-2

Authorized translation from the English language edition, entitled: HTML5 DEVELOPER'S
COOKBOOK; ISBN 0 321769 384; by Chuck Hudson and Tom Leadbetter; published by
Pearson Education, Inc, publishing as Addison Wesley.
Copyright © 20 12 Pearson Education, Inc.

Ali rights reserved. No part of this book may by reproduced or transmitted in any form
or by any means, electronic or mechanical, including photocopying, recording or by any
information storage retrieval system, without permission from Pearson Education, Inc.

Polish language edition published by HELION S.A. Copyright © 20 1 3.

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu


niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą
kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym,
magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądi


towarowymi ich właścicieli.

Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce
informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich
wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich.
Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne
szkody wynikłe z wykorzystania informacji zawartych w książce.

Wydawnictwo HELION
ul. Kościuszki 1c,
44- 100 GLIWICE
3223 1 22 19, 32230 98 6 3
tel.
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl!user/opinie/htm15p
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

Pliki z przykładami omawianymi w książce można znaleźć pod adresem:


ftp://ftp.helion.pl!przyklady/htm15p.zip

Printed in Poland.

• Poleć książkę na Facebo ok.com • Księgarnia internetowa


• Kupwwersji papierowej • Lubię to! » Nasza społeczność
• Oceń książkę
Dla Alexa, mojego dziadka -dziękuję ci za dzielenie się swoją miłością do życia i książek
- Chuck

Dla Lucy -dzięki za bycie sobą


-Tom
Spis treści

Wstęp . ......................................................................................................13

Podziękowania . .........................................................................................21

O autorach . ...............................................................................................23

Rozdział 1 Nowe elementy strukturalne w HTML5 ........................................................25


PRZEPIS DLA POCZĄTKUJ ĄCYC H
Budowa startowego dokumentu HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
doctype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Kodowan ie znaków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
J avaScript i łącza CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Styl używanej s kładn i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Skąd pochodzą wszystkie nowe e lementy? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Wykorzystanie elementu header do utworzen i a nagłówka witryny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Wykorzystanie elementu hgroup do gru powania nagłówków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie nawigacj i przy użyciu e lementu nav . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Użycie elementu article . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Gru powanie treści przy użyciu elementu section . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Którego elementu powin ieneś używać: article czy section? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie paska bocznego za pomocą elementu aside . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Użycie elementu footer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Wykorzystanie narzędzia HTML5 Outl iner do utworzenia prawidłowej
struktury dokumentu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
PRZEPIS DLA ZAAWANSOWANYCH
Wykorzystanie wszystkich nowych elementów do utworzen i a strony z wiadomościa m i . . . . 43
6 Spis treści

PRZEPIS DLA ZAAWANSOWANYCH


Wykorzystanie wszystkich nowych elementów do utworzen i a strony
z wyn i kam i wyszu kiwania . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

Rozdział 2 Grupowanie, poziom tekstu i zmiany semantyczne . ...................................... 51


PRZEPIS DLA POCZĄTKUJ ĄCYC H
Oznaczanie rysu n ków i ich podpisów elementa m i figure i figcaption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Oznaczanie daty i czasu za pomocą e lementu time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie widżetu przełączn i ka za pomocą elementu details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Użycie elementu address do danych kontaktowych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Podświetlanie tekstu za pomocą elementu m ark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Użycie elementu s do pokazania niepoprawnej l u b nieistotnej treści . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Zm iany dotyczące istniej ących elementów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Element cite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Element ol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Element dl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Element smali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Elementy b oraz strong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Elementy i oraz em . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Element abbr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Element hr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Elementy, których już nie ma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
PRZEPIS DLA POCZĄTKUJ ĄCYC H
O pakowywanie elementów łączam i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Dodawanie inform acj i semantycznych za pomocą m i krodanych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Stosowanie WAl-ARIA z HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
PRZEPIS DLA ZAAWANSOWANYCH
Oznaczanie komentarzam i strony z artykułem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1

Rozdział 3 Obsługa przeglądarek w HTML5 .................................................................. 73


PRZEPIS DLA POCZĄTKUJ ĄCYC H
Obsługa I nternet Explorera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Użycie J avaScriptu do uzyskania kompatybi l ności z HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Uzyskiwanie kom patybi l ności CSS-a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Szablony (boi lerplates) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Testowanie działa n i a nowych możl iwości HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Wykorzystanie jQuery do zastąpienia kalendarza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7
Spis treści 7

PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH


Wykorzystanie bibl ioteki Modernizr do wykrywan i a możl iwości . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Polyfi l l ing (wielowypełn ianie) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Użyteczne strony s prawdzające HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

Rozdział 4 Nowe techniki dotyczące układu i stylizacji w CSS3 . . .................................. 85


PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Tworzenie dostosowującego się proje ktu za pomocą CSS3 Med ia Queries . . . . . . . . . . . . . . . . . . . 85
Kiedy używać zapytań o media . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Obsługa iPhone'a i urządzeń z systemem Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Korzystanie z własnych czcionek przy użyciu @font-face . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Formaty p l i ków i działanie czcionek w różnych przeglądarkach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Usługi związane z kroj a m i pisma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Tworzenie przycisków za pomocą gradientów CSS i wielu teł . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Upiększanie witryny za pomocą transformacj i i przejść . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
PRZEPIS DLA ZAAWANSOWANYCH
Tworzenie a n i m acj i za pomocą CSS-a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

Rozdział 5 Formularze internetowe HTML5 . .............................................................. 107


Walidacj a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Typy wejściowe HTML 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie form u larza do danych kontaktowych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
in put type="email" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
in put type="tel" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
in put type="url" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie form u larza wyszu kiwania przy użyciu in put type="search" . . . . . . . . . . . . . . . . . . . . . . . . . 112
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie kontrolek kalendarza i czasu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
in put type="datetime" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
in put type="datetime-Iocal" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
in put type="date" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
in put type="time" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
in put type="month" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
in put type="week" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Nakładanie ograniczeń na daty i godziny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie pola do wprowadzan i a l iczby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie suwaka (bez potrzeby użycia J avaScriptu) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie próbn i ka kolorów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
8 Spis treści

PRZEPIS DLA POCZĄTKUJ ĄCYC H


Wyświetlanie wyni ków za pomocą elementu output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Użycie tekstu zastępczego form u larza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Autouzupełnianie za pomocą atrybutu l ist i elementu datalist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Ś ledzenie postępu wykonania zad a n i a za pomocą e lementu progress . . . . . . . . . . . . . . . . . . . . . . . . 121
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Wskazywanie wyn i ku pom iaru za pomocą elementu meter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Przechodzenie do elementu form po załadowan i u strony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Zezwalanie na wiele wartości . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Prosta walid acja przy użyciu atrybutu requ i red . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Pisanie własnych reguł walid acj i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Nakład anie ograniczeń na wprowadzane dane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
step . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
m i n i m ax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
formnovalidate i novalidate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Dostosowywanie form u larza i nadawanie mu stylu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Kom u n i katy błędów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
PRZEPIS DLA ZAAWANSOWANYCH
Wszystko razem - tworzenie form u larza rejestracyjnego . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

Rozdział 6 Rysowanie na płótnie . ............................................................................. 133


Element canvas - inform acje ogólne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Początki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Ws półrzędne x i y . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Nałożenie s i atki na płótno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Narzędzia płótn a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie prostych kształtów i l i n i i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Rysowanie i form atowanie prostokąta lub kwadratu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Stosowanie gradientów w kształtach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Rysowanie l i n i i i ścieżek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Rysowanie wielokątów za pomocą ścieżki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Rysowanie łu ków i o kręgów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Rysowan ie krzywych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Spis treści 9

PRZEPIS DLA POCZĄTKUJ ĄCYC H


Dodawanie tekstu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Wstawianie obrazka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Przycinanie obrazka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
An im acj a m a py s prite'ów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Przekształcen i a płótn a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
PRZEPIS DLA ZAAWANSOWANYCH
An imowanie obrazka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
PRZEPIS DLA ZAAWANSOWANYCH
An im acj a pionowego wykresu słu pkowego . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

Rozdział 7 Osadzanie wideo w HTML5 . .....................................................................163


PRZEPIS DLA POCZĄTKUJ ĄCYC H
Dołączanie filmów za pomocą elementu video . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Obsługa w przeglądarkach i urządzen iach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
HTML5 i kodeki wideo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Dlaczego kodeki powinny Cię interesować? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Włączan ie wideo we wszystkich przegl ądarkach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Dodawanie zastępczej treści d l a starszych przeglądarek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Nowe atrybuty wideo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Tworzenie wideo z n apisami . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Inne możl iwości form atowan i a napisów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
API med iów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
PRZEPIS DLA ZAAWANSOWANYCH
Tworzenie n iestandardowych kontrolek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183

Rozdział 8 Osadzanie dźwięku w HTML5 . ..................................................................185


PRZEPIS DLA POCZĄTKUJ ĄCYC H
Um ieszczan ie dźwięku za pomocą elementu audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Um ieszczan ie dźwięku we wszystkich przeglądarkach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Dodawanie zastępczej treści d l a starszych przeglądare k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Nowe atrybuty elementu audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
API med iów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Tworzenie m i ksera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
PRZEPIS DLA ZAAWANSOWANYCH
Dodawanie internetowego rad ia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10 Spis treści

Rozdział 9 Dokonywanie zmian w historii przeglądarki . ............................................... 203


Podstawy h istorii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Kom patybi lność przeglądarek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Dodawanie do h istorii wpisów za pomocą pushState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie przeglądarki obrazków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Pobieranie stanu w przeglądarce obrazków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Z m i a n a h istorii za pomocą replaceState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Z m i a n a h istorii strony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 13
PRZEPIS DLA ZAAWANSOWANYCH
Używanie zaawansowanych obiektów d anych stanu do przenoszenia informacj i
pom iędzy stronam i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Testowanie bezpieczeństwa h istorii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 18
Pomocne b i b l ioteki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221

Rozdział 10 Wykorzystanie Geolocation API do uzyskania informacji


o położeniu geograficznym . ...................................................................... 223
Omówienie Geolocation API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Kom patybi lność przeglądarek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Gdzie na świecie się znajdujesz - getCurrentPosition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Ustalanie położenia geograficznego za pomocą zwykłego wywołania
getCurrentPosition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Poufność inform acj i o położeniu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Wyświetlanie na m apie inform acj i o lokal izacj i przy użyciu getCurrentPosition . . . . . . . . . . . . . 228
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
O kreślanie od ległości za pomocą o pcj i lokal izacj i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
PRZEPIS DLA ZAAWANSOWANYCH
Podążanie za poruszającym się obiektem dzięki watchPosition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

Rozdział 11 Przechowywanie danych po stronie klienta . . . . . ........................................... 247


Przechowywanie danych po stronie klienta - przegląd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Bezpieczeństwo danych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Klucze i wartości - sess ionStorage i localStorage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Pobieranie i ustawianie danych w pam ięci sesji . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Narzędzia dla program istów Chrome służące do oglądania zawartości pam ięci . . . . . 252
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Nadawanie stylów z pam ięci sesj i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Spis treści 11

PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH


Za pisywanie form u larzy za pomocą lokalnego przechowywan ia danych . . . . . . . . . . . . . . . . . . . . . . . 257
PRZEPIS DLA ZAAWANSOWANYCH
Przechwytywanie zdarzeń w pamięci lokalnej . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Web SQL Database API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
PRZEPIS DLA ZAAWANSOWANYCH
Użycie s ieciowej bazy danych do stworzen i a l isty zaku pów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277

Rozdział 1 2 Komunikacja i wątki . ............................................................................... 279


Przegląd WebSocket API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Rozm awianie przez gniazda sieciowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
Realizacja wielowątkowości za pomocą wątków roboczych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie wątku roboczego . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Dodawanie dwu kierun kowej kom u n i kacj i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
PRZEPIS DLA ZAAWANSOWANYCH
Wykorzystywan ie wspóldzielonych wątków roboczych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296

Rozdział 13 Zachowanie przeglądarek w HTML5 .......................................................... 297


Drag and Drop API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Przeciągan ie i u puszczanie pomiędzy elementa m i d iv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
PRZEPIS DLA ZAAWANSOWANYCH
Wykorzystanie zdarzeń i obiektu dataTransfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
Pam ięć podręczna a p l i kacj i i jej API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
Pam ięć podręczna przeglądarki a bezpieczeństwo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 10
Odwolania do p l i ku man ifestu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 10
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Tworzenie p l i ku man ifestu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 10
CAC H E . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
FALLBAC K . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
N ETWORK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Aktu a l izacja pam ięci podręcznej poprzez man ifest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Korzystanie ze stron internetowych offl ine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 12
API pamięci podręcznej apli kacj i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 15
Notification API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 16
U prawn ienia dotyczące powiadomień . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Kom patybi lność przeglądarek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Wyświetlanie prostego powiadomienia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 18
PRZEPIS DLA ZAAWANSOWANYCH
Tworzenie strony powiadomień o tweetach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
12 Spis treści

Rozdział 14 Praca z plikami lokalnymi . ....................................................................... 331


Przegląd Fi le API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Bezpieczeństwo Fi le API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Odczyt atrybutów p l i ku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Przetwarzanie wielu p l i ków za pomocą przeciągan i a i u puszczan i a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
I nterfejs Fi leReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Podgląd obrazków przy użyciu readAsDataU R L . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
PRZEPIS DLA ZAAWANSOWANYCH
Parsowanie p l i ku CSV za pomocą readAsText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
Rozszerzone s pecyfikacje Fi le API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
PRZEPIS DLA ZAAWANSOWANYCH
Tworzenie lokalnego p l i ku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355

Rozdział 15 I ntegracja z urządzeniami przenośnymi ...................................................... 357


Krótka h istoria API u rządzeń . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Contacts API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
PRZEPIS DLA POCZĄTKUJ ĄCYC H
Pobieranie wszystkich konta któw i numerów telefonu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Messaging API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
Network I nformation API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
HTML Med ia C a pture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Przechwytywanie obrazu za pomocą e lementu in put typu fi le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
DeviceOrientation Event Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
PRZEPIS DLA Ś REDNIO ZAAWANSOWANYCH
Tworzenie poziom icy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Podsumowan ie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373

Przepisy . . ................................................................................................ 375

Skorowidz . . ............................................................................................. 379


Wstęp

H IM L
(ang. Hypertext Markup Language) j est podstawowym językiem służącym do tworzenia
i budowania witryn internetowych. Używany przez programistów od ponad 20 lat, przez
pierwszych kilka był poddawany radykalnym zmianom, ale w końcówce minionego stulecia jego rozwój
nieco się spowolnił. Aż do teraz.

Krótka historia HTML-a


W 1991 roku sir Tim Berners-Lee napisał dokument pod tytułem HTML Tags, przedstawiaj ący 20 elementów,
które miałyby służyć do tworzenia dokumentów internetowych. W 1 993 roku stowarzyszenie Internet
Engineering Task Force (IETF) opublikowało propozycję pierwszej specyfIkacji HTML. Projekt tej propozycji
wygasł, a pierwsza specyfIkacja - nie HTML 1 .0, ale HTML 2.0 - została opublikowana dopiero
w listopadzie 1 995.
W 1 997 roku została opublikowana specyfIkacja HTML 3.2, a po niej, w 1 998 roku, HTML 4. HTML 4
miał trzy warianty: Strict, Transitional oraz Frameset. W tym okresie producenci przeglądarek, tacy jak
Microsoft i Netscape, rozpoczęli implementowanie HTML-a w odrobinę różny sposób, co rozpoczęło pierwszą
rundę wojen przeglądarek (http://pl.wikipedia.org/wiki/Historia-przegl%C4%85darek_internetowych).

XHTML
Po publikacji HTML 4 organizacja World Wide Web Consortium (W3C) zdecydowała się na wstrzymanie
rozwoju HTML-a i zajęła się pracą nad opartym o XML językiem XHTML (ang. Extensible Hypertext
Markup Language), który był uważany za przyszłość internetu. W XHTML-u nie było nowych elementów
- specyfIkacja była dokładnie taka sama jak w przypadku HTML 4, ale programiści mieli stosować się do
nowych reguł składniowych: zamykania znaczników i umieszczania wartości atrybutów w cudzysłowach.
Oznaczało to bardziej ścisłe, sztywne standardy kodowania i wymuszało na programistach stosowanie tego
samego stylu pisania kodu. Mniej więcej w tym samym czasie, w 2001 roku, coraz ważniejsze i popularniejsze
zaczęły się stawać kaskadowe arkusze stylów (CSS), a wraz z upowszechnieniem się blogowania wzrosło
znaczenie standardów sieciowych.
Począwszy od 2002 roku, W3C publikowało szkice XHTML 1 . 1 . Podczas gdy XHTML 1 był w gruncie
rzeczy HTML-em z odrobiną XML-a, XHTML 1 . 1 był właściwie XML-em. Chociaż miał być standardem
14 Wstę p

przyszłościowym, nie był kompatybilny wstecz - jeżeli był używany do tworzenia witryny, nie działała
ona w starszych przeglądarkach.
Wywołało to niepokój w społeczności sieciowej, u producentów przeglądarek, a nawet w samym W3e.
Nie każdy był przekonany, że XML jest przyszłością języka znaczników w sieci.

Web Forms i Web Applications oraz WHATWG


W 2004 roku przedstawiciele firm Apple, Mozilla i Opera rozpoczęli prace nad własną specyfikacj ą,
nakierowaną na utworzenie kompatybilnego wstecz kodu, który mógłby być używany do tworzenia aplikacji
internetowych. W3C odrzuciło tę propozycję, co doprowadziło do uformowania się grupy pod nazwą Web
Hypertext Application Technology Working Group (WHATWG) .
Grupa WHATWG rozpoczęła prace nad standardami Web Forms 2.0 oraz Web Applications 1 .0.
Redaktorem specyfikacji został lan "Hixie" Hickson, a problemy i pomysły rozpatrywano przy użyciu
publicznej listy mailowej . Chociaż początkowo pracowano nad dwoma specyfikacjami, Web Forms 2.0
i Web Applications 1 .0, zostały one połączone w jedną - HTML5.

XHTML 2 kontra HTMLS


A zatem, podczas gdy WHATWG rozwijał HTML5, w W3C marniała specyfikacja XHTML 2. W 2006
roku W3C przyznało się do błędnej decyzji o niegdysiejszym porzuceniu HTML-a i chociaż nadal rozwijało
XHTML 2, zaczęło ponownie przyglądać się rozwojowi tego pierwszego. W3C zdecydowało się wykorzystać
pracę wykonaną dotychczas przez WHATWG jako punkt początkowy dla nowej wersji HTML-a.
Chociaż była to dobra wiadomość, doprowadziła ona do niejasnej sytuacji, gdyż prace obejmowały teraz
trzy specyfikacje: dwie w W3C (HTML 5 oraz XHTML 2) i jedną (HTML5) w WHATWG (zauważ brak
spacji w porównaniu z "HTML 5" organizacji W3e). A zatem specyfikacj a HTML5/HTML 5 była rozwijana
w tym samym czasie przez dwie grupy.
WHATWG operuje w odmienny od W3C sposób i j est zdolna do działania w znacznie szybszym
tempie. Dzięki istnieniu w WHATWG publicznej listy mailowej pomysły były przedstawiane regularnie,
a programiści stron internetowych mieli możliwość kwestionowania niektórych spośród podjętych decyzji.
Zespół pracujący nad specyfikacj ą był w stanie, i nadal j est, szybko implementować dobre pomysły, odrzucać
złe oraz zmieniać lub usuwać części specyfikacji na podstawie reakcji społeczności. HTML5 był rozwijany
znacznie szybciej w WHATW G niż w W3e.
Po kilku latach, w 2009 roku, W3C ogłosiło zaprzestanie prac nad XHTML 2. HTML5 zwyciężył.
W j akim punkcie znajduje się zatem specyfikacja? Cóż, nadal jest rozwij ana głównie przez WHATWG,
a następnie przejmuje ją W3C i poddaje przeglądowi.
Proces nie jest w pełni idealny, ale wyniki są obiecujące.

z której specyfikacji korzystać?


Jedna wersja specyfikacji znajduje się na witrynie W3C (http://dev.w3.org/htmI5/spec/spec.html), a druga na
witrynie WHATWG (http://www.whatwg.orglspecs/web-apps/current-work/multipagej). Obie są naprawdę
potężnymi dokumentami. W marcu 20 1 1 roku Ben Schwarz uruchomił stronę zawieraj ącą specyfikację
HTML5 dla programistów witryn internetowych (http://developers.whatwg.org), której sugerujemy używać.
Jeśli jesteś zainteresowany codziennymi dyskusjami, możesz również śledzić na bieżąco prace nad specyfikacją
poprzez listę mailową: http://lists.whatwg.orglhtdig.cgi/whatwg-whatwg.org.
Czym dokładnie jest HTM L5? 15

Podstawowa specyfIkacja HTMLS jest dokładnie taka - podstawowa. Nastąpiła istotna zmiana mająca
na celu dopasowanie się do szybkości, z jaką rozwija się technologia. Nowe, dodatkowe specyfikacje są
przez cały czas opracowywane - zarówno przez zespoły w WHATWG, j ak i w W3C - i dotyczą różnych
kwestii, takich jak informacj e o połączeniach sieciowych czy urządzeniach rejestrujących obraz. Producenci
przeglądarek współpracują, by pomóc zdefIniować te funkcjonalności i przyspieszyć ich włączenie do swoich
przeglądarek. Następne lata będą na pewno niezwykle ekscytujące.

Założenia HTML5
HTMLS utworzono tak, aby obsługiwał istniej ącą treść, czyli innymi słowy, jest on kompatybilny wstecz.
Główną przeszkodą w pracach nad specyfIkacją XHTML 2 była obawa przed nieprawidłowym wyświetlaniem
większości witryn. HTMLS został zbudowany na fundamentach HTML 4, tak aby przeglądarki mogły nadal
obsługiwać HTML - nie tylko nowe elementy HTMLS, ale wszystko to, co znajduje się w HTML 4. Witryny,
które działają teraz w HTML 4 lub XHTML, powinny prawidłowo działać w HTMLS.
Używanie HTMLS oznacza, że możesz nadal kodować w stylu, z którego korzystałeś wcześniej . Przy
projektowaniu HTMLS wzięto pod uwagę głosy programistów, więc nadal możesz używać składni HTML
lub XHTML, a przeglądarki będą wiedziały, co robić. Opiszemy to dokładniej w rozdziale 1 .
HTMLS wskazuje również przeglądarkom, jak powinny reagować na błędy spowodowane przez
nieprawidłową implementację znaczników. Wcześniej przeglądarki interpretowały błędy samodzielnie,
a więc każda przeglądarka miała własne standardy zachowania. HTMLS utworzono dla programistów
takich jak my oraz dla producentów przeglądarek, tak aby w niezbyt odległej przyszłości wszyscy pracowali
według tych samych standardów.
Być może prawdziwą siłą HTMLS j est to, j ak odpowiada na potrzeby programistów aplikacji
internetowych. Ze względu na coraz większe możliwości przeglądarek możemy tworzyć przypominaj ące
aplikacje witryny, które umożliwiają współdzielenie zdjęć, rysowanie, edycję plików oraz wykonywanie
innych zadań. Wcześniej funkcje te wymagały warstw J avaScriptu oraz wtyczek, takich jak J ava lub Flash
- oznaczało to problemy z dostępnością oraz zależność od stabilności zewnętrznego oprogramowania.
HTMLS daje nam nowe standardy dotyczące tworzenia aplikacji internetowych z posiadającymi ogromne
możliwości API dla takich dziedzin j ak obszary, na których można rysować, przeciąganie i upuszczanie,
lokalne przechowywanie danych oraz bezpośrednia obsługa wideo w przeglądarce. Wraz z wyspecyfIkowaniem
standardów przeglądarki za j akiś czas będą obsługiwać te funkcj e prawidłowo oraz w stabilny sposób .
Społeczność sieciowa będzie pomagać w rozwijaniu standardów, stale je weryfIkując i ulepszając, a programiści
nie będą musieli używać sztuczek umożliwiających działanie tych funkcji.

Czym dokładnie jest HTML5?


HTMLS nie j est j edną technologią. To raczej nadrzędne pojęcie, którym przyjęto oznaczać połączenie
nowych oraz ulepszonych elementów HTML, stylów CSS oraz javascriptowych API i zdarzeń. Technologie
te udostępniają wspólnie szeroki zakres nowych funkcji zwiększających łatwość użycia witryn, sprawiających,
że bardziej przypominają one zwykłe aplikacje, oraz służących do integracji z urządzeniami. Na poniższej
liście przedstawiono jedynie niektóre spośród dostępnych nowych lub ulepszonych funkcji:
• ulepszona semantyka,
• formularze,
16 Wstę p

• rysowanie na płótnie,
• funkcja "przeciągnij i upuść",
• lokalne przechowywanie danych,
• przesyłanie wiadomości między stronami,
• powiadomienia na pulpicie,
• audio i wideo,
• gniazda sieciowe,
• geolokalizacja,
• historia,
• mikro dane.

Chociaż, ściśle rzecz biorąc, geolokalizacj a nie j est częścią specyfikacji HTMLS, stanowi przydatną,
nową technologię rozwijaną równolegle, w związku z czym mówi się o niej w kontekście HTMLS. A zatem
tak, my również będziemy mówić o geolokalizacji w tej książce.
Nie wszystko, co w programowaniu internetowym nowe, j est związane z HTMLS. CSS3 nie j est tym
samym co HTMLS, ale ponieważ jest nowy i niezwykle popularny, j est zaliczany do tej samej kategorii. CSS
jest zupełnie innym językiem i technologią niż HTML. CSS dotyczy prezentacji; HTML - struktury. Możesz
zrobić kilka całkiem fantastycznych rzeczy przy użyciu CSS3, ale programiści i społeczność powinni być
świadomi tego, że istnieje między tymi językami różnica.
A chociaż nie chcemy włączać się do debaty na temat tego, czy HTMLS jest zabój cą Flasha (nie jest),
istotą HTMLS jest to, że istnieje teraz ogromna ilość funkcji bezpośrednio wbudowanych w przeglądarkę
i wszystkie one należą do standardu.

Czy HTML5 ma logo?


Tak, HTMLS ma logo. W przeszłości programiści stron internetowych i właściciele witryn umieszczali
na swoich witrynach ikony, by za ich pośrednictwem poinformować, że przestrzegają różnych wytycznych
W3C, takich jak te dotyczące (X)HTML-a, CSS-a albo dostępności. Tendencja ta ostatnio nieco osłabła,
lecz na początku 20 1 1 roku W3C opublikowało szereg różnych logo, które początkowo wywołały dość duże
zamieszanie w społeczności sieciowej, ponieważ zgrupowały wiele technologii internetowych, włącznie
z CSS3, pod szyldem HTMLS. Tak jak wspomnieliśmy, CSS3 nie jest tym samym co HTMLS, a ponieważ
W3C pozornie twierdziło coś przeciwnego, społeczność sieciowa wyraziła swój niepokój .
Na szczęście W3C zmieniło cele i definicje swoich logo, więc główne logo (rysunek W. l ) "reprezentuje
HTMLS, kamień węgielny nowo czesnych aplikacji internetowych", a mniejsze logo (rysunek W.2)
"reprezentują aspekty nowoczesnych aplikacji i witryn internetowych - styl, semantykę, grafikę i tak dalej".
A zatem logo są przeznaczone dla programistów, którzy chcą zademonstrować swoje wsparcie dla
kluczowych standardów sieciowych. Używanie ikon albo logo nie jest wymagane, ale możesz z nich skorzystać,
jeśli chciałbyś poinformować użytkowników o zastosowaniu przez Ciebie różnych funkcji. Pod adresem
http://w3.orglhtml!logo są dostępne logo, a także ich kreator, który pozwala wybierać różne ikony i style.
Styl przepisów ku l i narnych 17

HTML

Rysunek W. l. Logo HTML5 (logo HTML5 jest autorstwa organizacji W3C, www.w3. org)

o c
Rysunek W. 2 . Mniejsze ikony reprezentujące różne technologie (od lewej do prawej):
dostęp do urządzeń, 30, grafika i efekty, CSS3, semantyka, multimedia, łączność,
wydajność i integracja, lokalne przechowywanie danych
(te ikony również są autorstwa organizacji W3C, www.w3. org)

Styl przepisów kulinarnych


Niniejsza książka jest zaprojektowana w taki sposób, by uczyć poprzez pokazywanie przykładów, i nawiązuje
do stylu przepisów kulinarnych, czyli objaśnia temat i udostępnia przepisy z nim związane, W większości
przypadków przepisy są próbą zobrazowania możliwości zastosowania technologii do rozwiązywania
problemów związanych z kodowaniem raczej w świecie rzeczywistym niż w przykładach, które nie mają
konkretnego celu, Mamy nadzieję, że analogicznie do przepisów używanych w kuchni, i te przepisy uznasz
za wartościowe punkty odniesienia do własnych rozwiązań programistycznych,
Ogólnie rzecz biorąc, książka została podzielona w taki sposób, by na początku zaprezentować prostsze
tematy, a na ich podstawie przejść do tematów bardziej złożonych, W pierwszych rozdziałach zajmiemy się
elementami HTMLS i uzupełnieniami CSS3, a w kolejnych omówimy różne javascriptowe API i zdarzenia,
Zdajemy sobie sprawę z tego, że na temat każdego z tych zagadnień można by napisać całą książkę, Dotyczy
to szczególnie standardów, takich jak CSS3, na którego temat dostępnych jest wiele dobrych książek
Do wszystkich zagadnień omówionych w danym rozdziale dołączyliśmy tabele, które pokazują poziom
obsługi danej funkcji w popularnych przeglądarkach, Uwzględniliśmy co najmniej jeden przepis obrazujący,
w jaki sposób daną technologię można zastosować, W większości przypadków przepisy składają się z podanej
w krokach instrukcji oraz co najmniej jednego fragmentu kodu (listingu), po którym następuje opis przepisu,
Listingi zostały również opublikowane pod adresemftp://ftp,helion,pl/przyklady/h tmlSp,zip,

Uwaga
Niektóre kom ponenty HTML5, takie jak j avascri ptowe API , są wciąż do pracowywane w specyfikacjach
i i m plementowane w różnych przeglądarkach . W tej ks i ążce s k u p i l iśmy się na elementach , które
są dobrze wys pecyfi kowane oraz obsługiwane przez co naj m n iej jedną spośród naj popu larn iejszych
przeglądare k . Na leży j e d n a k pam iętać o tym , że H T M L5 jest stale rozbudowywany o nowe dod atki
oraz fun kcj e . W rozdziale 15. omówil iśmy niektóre spośród możl iwości i nowych fun kcj i dotyczących
integracj i urządze ń .
18 Wstę p

Zaprezentowane w książce przepisy zostały podzielone na trzy kategorie: dla początkujących, średnio
zaawansowanych oraz zaawansowanych. Kategorie te służą zobrazowaniu trudności omawianego tematu
i jednocześnie stanowią podstawę oszacowania wysiłku i czasu, który musisz przeznaczyć na ukończenie
przepisu. To oczywiście kategorie subiektywne - wiemy, że czas i wysiłek dla różnych czytelników mogą
być zupełnie różne.

Zewnętrzne b iblioteki
Jeśli kiedykolwiek byłeś zaangażowany w tworzenie stron internetowych, jedną z pierwszych rzeczy, którą
zauważysz w przepisach z niniej szej książki, j est znikoma liczba odwołań do j avascriptowych bibliotek
zewnętrznych, takich jak choćby j Query. Jest kilka drobnych wyjątków od tej reguły, kiedy staramy się
przezwyciężyć określone przeszkody przy użyciu zewnętrznych bibliotek. Jednak ogólnie rzecz biorąc,
próbowaliśmy się skupić na rdzeniu technologii HTMLS, gdyż każdy czytelnik będzie miał swój własny
zbiór ulubionych bibliotek, a każda biblioteka będzie miała swoje własne metody obsługi funkcji HTMLS
i integracji z nimi.
Mocno wierzymy, że biblioteki odgrywają istotną rolę w codziennym projektowaniu stron internetowych
oraz rozwijaniu witryn i aplikacji. Sami również mamy ulubione biblioteki. W wielu przypadkach wykorzystanie
funkcji HTMLS w bibliotekach, które j uż takie funkcj e udostępniaj ą, przypomina ich użycie w czystym
HTMLS. A zatem zrozumienie tego, j ak używać komponentu w zwykłym JavaScripcie, pozwoli Ci łatwiej
wykorzystać komponent w bibliotece, którą wybrałeś.

Materiały towarzyszące książce


Z założenia książka taka jak ta zawiera mnóstwo listingów uzupełniaj ących tekst podawanych przepisów.
Przykłady kodu zostały udostępnione na serwerze FTP pod adresemftp://ftp.helion.pl/przyklady/htmlSp.zip.

Kiedy HTML5 będzie gotowy?


Mówi się, że programiści nie mogą lub nie powinni rozpoczynać pracy z HTMLS j uż teraz, ale to błędne
podejście.

Czy trzeba czekać aż d o 2022 roku?


Nie, nie musisz czekać aż do 2022 roku! W wywiadzie z 2008 roku redaktor HTMLS, lan Hickson, nakreślił
horyzont czasowy, zgodnie z którym HTMLS będzie gotowy dopiero w 2022 roku. Stwierdzenie to zostało
przesadnie rozdmuchane w mediach i społeczności sieciowej . Wydaje nam się, że Hickson miał na myśli to,
iż ostateczne zalecenia zostaną opublikowane dopiero około 2022 roku, ale to nie oznacza, że nie możesz
zrobić użytku z wszystkich tych funkcji, które już zostały zdefiniowane i włączone do specyfikacji. Jak mogłeś
wywnioskować z dyskusji dotyczącej historii HTML-a, przygotowanie specyfikacji wymaga dużego wysiłku
i zajmuje ogromne ilości czasu, a specyfikacja musi przejść przez wiele etapów, zanim zostanie ukończona.
Można tutaj przytoczyć argument związany ze standardem CSS 2 . 1 . CSS 2 . 1 był w fazie rozwoj u przez
ponad 10 lat i został ukończony dopiero latem 20 1 1 roku, ale w latach, kiedy był rozwijany, wszyscy i tak
używaliśmy CSS-a, prawda? Jesteśmy teraz na etapie CSS3 i kto wie, kiedy będzie on "gotowy". To doskonały
przykład na to, j ak społeczność sieciowa posuwa technologię i przyszłość sieci naprzód, wyprzedzaj ąc
specyfikacje.
Kiedy HTML5 będzie gotowy? 19

Czy wobec tego mogę już używać HTML5?


Tak, możesz już teraz używać HTMLS! Nie będzie momentu, w którym będziesz musiał zaprzestać używania
HTML 4 i rozpocząć korzystanie z HTMLS. Kto wie, kiedy W3C ogłosi, że HTMLS j est oficjalnie gotowy
- możemy już wszyscy używać wówczas funkcji HTML6. Producenci przeglądarek jednak całym sercem
akceptują i przez cały czas implementują nowe funkcje HTMLS.
Ponieważ w ramach głównej i dodatkowych specyfIkacji zawarto tak wiele różnych elementów i technologii,
możesz swobodnie wybierać te części HTMLS, z których chcesz korzystać - nie ma mowy o wyborze na
zasadzie "wszystko albo nic". Nie wszystkie przeglądarki zachowują się zawsze prawidłowo, lecz w rozdziale 3.,
"Obsługa przeglądarek w HTMLS", pokażemy niektóre metody radzenia sobie z przeglądarkami. Wszystkie
najnowsze wersje przeglądarek Firefox, Safari, Opera, Chrome i Internet Explorer obsługuj ą szeroki, choć
w każdym przypadku nieco inny, zakres funkcji HTMLS. Jednak nawet w czasie pisania tej książki z dnia na
dzień napotykaliśmy w przeglądarkach znaczące ulepszenia, a dopóki przeglądarki będą ze sobą konkurować,
taka tendencja się utrzyma.
A zatem zaopatrz się w zestaw przeglądarek, edytor tekstu lub HTML-a i rozpocznij przygodę z HTMLS.
20 Wstęp
Po dziękowania

Podziękowania od Chucka Hudsona


Ilekroć podejmowany jest wymagający (taki jak ten) projekt, w doprowadzenie go do szczęśliwego końca
zaangażowanych j est wielu ludzi. Miałem szczęście, by podjąć się pisania tej książki wraz z Tomem
Leadbetterem, który j est niezwykle utalentowanym proj ektantem i programistą. Dziękuję ci, Tom, za
dzielenie się swoją wiedzą, testowanie mojej i bycie wspaniałym krytykiem wielu moich szalonych pomysłów.
Podziękowania dla mojej rodziny: mamie dziękuję za wsparcie; tacie za skrócenie ścieżki biznesowej ,
a dziadkom za ich miłość. Dla moj ej malutkiej Sierry: ty zawsze sprowadzasz uśmiech na moj ą twarz, wciąż
pytaj ąc "dlaczego"; zawsze bądź ciekawa świata. A szczególnie dla Michele - za jej nieustanne wsparcie
dla mojego uzależnienia od najnowszych technologii i niekończących się "projektów". Twoje zrozumienie,
gdy wielokrotnie byłem pełen udręki z powodu jednego wiersza kodu, znaczy dla mnie wszystko.
Tomie S., dzięki za wiele śmiechu przez te wszystkie lata. Nie mogę się doczekać kolejnych przygód,
jakie są przed nami.
Na koniec podziękowania dla wszystkich moich przyjaciół i mojej rodziny za czas dany mi na ukończenie
tego projektu; wy wszyscy nauczyliście mnie, że dzięki pasji i wytrwałości wszystko jest możliwe.

Podziękowania od Toma Leadbettera


Przede wszystkim niech mi będzie wolno podziękować mojemu współautorowi, Chuckowi Hudsonowi,
który pomógł mi w trakcie pisania i udzielał wartościowych uwag wtedy, gdy było to najbardziej potrzebne,
nie wspominaj ąc już o napisanych przez niego niektórych fantastycznych fragmentach kodu!
Podziękowania dla "Doktorów HTMLS" - Richa CIarka, Bruce'a Lawsona, Remy'ego Sharpa, Jacka
Osborne'a, Mike'a Robinsona, Oliego Studholme'a i Brandana Lennoxa - za wasz czas, umiejętności
i poświęcenie witrynie HTML5Doctor.com, która niezmiennie dostarcza społeczności sieciowej wspaniałej
wiedzy i tematów do dyskusji.
Wreszcie dla mojej żony, Lucy: dziękuję ci bardzo za wsparcie, gdy się męczyłem, i za cierpliwość,
gdy znikałem na wiele wieczorów i weekendów. Przygotowałem tę książkę najlepiej, j ak potrafiłem,
a ty pomagałaś mi przez cały czas.
22 Podziękowa n i a

Wspólne podziękowania
Dziękujemy Trinie MacDonald i wydawnictwu Pearson za postawienie na nas i nieustającą cierpliwość
związaną z naszymi niekończącymi się próbami ulepszania tekstu. Dziękujemy redaktorom, Michaelowi
Thurstonowi, Evanowi Burchardowi, Timowi Wrightowi, Siddharthowi Ramowi i Kimowi Wimpsettowi,
za rady i szczegółowe oraz wnikliwe uwagi, a także za dostrzeganie rzeczy, których sami nigdy byśmy nie
zauważyli. Wielokrotnie pozwoliliście nam dostrzec zza drzew las, a efekt końcowy jest dzięki temu znacznie
lepszy. Dziękuj emy za cały poświęcony czas i wysiłek. Wiemy, że było to spore wyzwanie, które łączyło się
z poświęceniem.
Na końcu chcielibyśmy podziękować całej społeczności HTMLS za dzielenie się swoją wiedzą oraz Tobie,
czytelniku, za bycie pionierem w prawdziwie ekscytuj ących czasach. Mamy nadzieję, że będziesz czerpał tyle
radości z czytania tej książki, ile my mieliśmy przy jej tworzeniu.
o autorach

Chuck Hudson od lat dziewięćdziesiątych zajmuje się pisaniem oprogramowania w branżach związanych
z siecią i urządzeniami przenośnymi. Jest odnoszącym sukcesy przedsiębiorcą. Dzięki swoj ej pasji do
rozwiązywania problemów biznesowych za pomocą technologii przeprowadzał na rzecz firm konsultacje
związane z różnymi technologiami sieciowymi oraz wygłaszał prezentacje na rozmaitych konferencjach.
Będąc stale szkolącym się maniakiem komputerowym, Hudson jest również certyfikowanym programistą
PHP, deweloperem PayPal oraz nauczycielem programowania internetowego, technologii mobilnych
i przedsiębiorczości w okolicach Bostonu i Atlanty. W 2008 roku otrzymał nagrodę eBay Star Developer
za pierwsze internetowe i natywne aplikacje przeznaczone na platformę iOS.

Tom LeadbeUer j est proj ektantem stron internetowych i programistą z Liverpoolu (Wielka Brytania).
Od 2005 roku działa w branży internetowej na rzecz różnych organizacji i klientów, a od początku 2009
roku eksperymentuj e z HTML5, na którego temat bloguje na witrynie HTML5Doctor.com.
24 O autorach
1
Nowe elementy strukturalne
w HTML5

W
HTMLS nie chodzi tylko o interaktywną magię związaną z j avascriptowymi API i wyświetlaniem
filmów. Przy tworzeniu stron internetowych możesz używać ponad 20 nowych elementów, które
wzbogacają semantykę i pozwalają tworzyć bardziej dostępne i łatwiejsze do ponownego wykorzystania treści.
W kolejnych rozdziałach poznasz nowe kontrolki formularzy oraz elementy HTMLS związane
z multimediami. W tym rozdziale dowiesz się o nowych elementach strukturalnych: header, hgroup, nav,
footer, art; c l e, sect ; on oraz as; de
- skupimy się na tym, jak, kiedy i dlaczego używać tych elementów,
czy to osobno, czy łącznie. Ś ciśle rzecz biorąc, zbudujesz prosty szablon strony internetowej zawierającej
nowe elementy, taki jak pokazany na rysunku 1 . 1 .

<heade r >

<nav>

<ar t i c l e > <a s i de>

< s ec t i on>

< s ec t i on>

< f o o t er >

Rysunek 1 . 1 . Struktura prostej strony zawierającej nowe elementy HTML5


26 Rozdział 1. Nowe e lementy stru kturalne w HTML5

PRZEPIS DLA POCZĄTKUJĄCYCH


Budowa startowego dokumentu HTML5
Skoro masz zacząć programować w HTMLS, rozpocznijmy od początku dokumentu HTML. Chociaż
zawartość tego podrozdziału nie obejmuje omówienia nowych elementów, trzeba wspomnieć, że pojawił
się nowy sposób zapisu elementów, o czym warto wiedzieć, zanim przejdziemy do elementu body.

d octype
Czy to wygląda znajomo?
< ! OOCTYPE html PUBLIC " -jjW3CjjOTO XHTML 1 . 0 Stri ctjjEN " '' http : //www . w3 . org/TR/xhtml 1/OTO/xhtml 1 -stri c t . dtd''>

Na samym początku, w pierwszym wierszu dokumentu HTML, powinna się znaleźć wymagana przez
standardy sieciowe definicja typu dokumentu (ang. Document Type Definition, DTD), czyli element
doctype. Informuje on przeglądarkę, jak przetwarzać dokument, i właśnie dlatego powinien być pierwszym
fragmentem pliku HTML. Gdybyś nie użył definicji doctype lub umieścił przed nią jakikolwiek inny kod,
przeglądarka zadziałałaby w tzw. trybie osobliwości i być może w niektórych przeglądarkach napisany przez
Ciebie kod nie zostałby prawidłowo zinterpretowany.
Nie sądzę, abyś chciał zapamiętywać podany doctype, bo niby z jakiej racji? Jest okropny i niezgrabny.
W HTMLS masz teraz ładny i łatwy do zapamiętania doctype:
< ! OOCTYPE html >

To naprawdę wszystko . Wszystko, czego potrzebujesz, by poinformować przeglądarkę, że działasz


w trybie standardów. Jeśli przeglądarka nie implementuje HTMLS, strona wciąż będzie działać. Gdybyś użył
< ! doctype htm1 5>, również spowodowałoby to przejście do trybu osobliwości. Ten doctype został wybrany
w taki sposób, aby zawsze działał w przeglądarkach, niezależnie od tego, jaka jest naj nowsza wersja języka.

I
Uwaga
Pod adresem http:;jinfomesh.netjhtm/jhistoryjearlyj możesz odnaleźć najwcześniejszy dokument
HTML z 13 l istopada 1990 roku . Użyty kod znaczn i ków jest n aprawdę prosty, co przypo m i n a nam
doctype z HTML5. Prawdę mówiąc, jeśli dodałbyś do tej strony nowy doctype, przeszłaby ona wa lidację .

Kod owanie znaków


Pierwszym wierszem, który powinien się znaleźć w treści elementu head, jest deklaracja cha rset, mówiąca
przeglądarce, w j aki sposób dany plik powinien być interpretowany; w tym przypadku chcesz jej wysłać
dokument HTML.
W HTML 4 deklaracja ta wyglądała w ten sposób:
<meta http-equ i v = " Content-Type" content = " textjhtml ; charset=utf-8 " >

Tak j ak w przypadku definicji doctype, w HTMLS jest t o znacznie prostsze:


<meta charset = " utf-8" j>

To łatwe! Pamiętaj, że potrzebuj esz definicji doctype oraz powyższej deklaracji na swojej stronie.
Budowa startowego dokumentu HTML5 27

J avaS cript i łącza CSS


Przez ten krótki podrozdział możemy przejść równie szybko jak przez poprzedni. HTMLS pomaga się pozbyć
z Twoich stron wielu znaczników i możesz dzięki niemu uprościć odwołania do JavaScriptu (a także innych
plików skryptowych po stronie klienta) oraz CSS-a. W HTML 4 elementy ser; pt i l ; n k wymagały atrybutu
type, tak jak w poniższym przykładzie:
<script type= " textjjavascri pt" src= "my-javascri pt-fi 1 e . j s " ><jscri pt>
<l i n k re1 = " sty1 esheet " type= " t extjc s s " href="my-css-fi 1 e . cs s " j>

W HTMLS wiersze te wyglądaj ą następuj ąco:


<scri pt src = " my-j avascri pt-fi 1 e . j s " ><jscri pt>
<l i nk re1 = " sty1 es heet " href="my-css -fi 1 e . cs s " j>

Czy zastanawiasz się, dlaczego możesz tak zrobić? Cóż, jedną z intencji autorów HTMLS było umożliwienie
kodowania w sposób bardziej zdroworozsądkowy. Jeśli więc umieszczasz łącze do skryptu, przeglądarka
zakłada, że jest to plik javascriptowy, a jeśli używasz re l = "styl esheet", oznacza to, że podłączasz plik CSS-owy.
I nie martw się, pominięcie atrybutu type nie powoduje żadnych problemów w starszych przeglądarkach.

Styl używanej skład ni


Pozostając przy wcześniejszych przykładach, w HTMLS możesz zakodować stronę na kilka nieznacznie
różniących się sposobów.
Możesz kodować, używając wielkich liter:
<SCR I PT SRC=" MY-JAVASCRI PT- F I L E . JS " ><jSCRIPT>

Możesz kodować, nie używając znaków cudzysłowu:


<scri pt src=my-j avascript-fi 1 e . j s><jscri pt>

Możesz pominąć zamykający ukośnik:


<l i nk re1 = " sty1 es heet " type=textjcss href=my-css-fi 1 e . css >

Lub możesz wykorzystać kombinację powyższych sposobów!


< L i N K re1 = " sty1 es heet " tYPe= " t extjc s s " href=my-css-fi 1 e . css j>

Wszystkie te sposoby są dozwolone, jednak stanowczo zaleca się, aby wybrać jakiś styl i konsekwentnie
się go trzymać. Będzie to użyteczne nie tylko dla Ciebie, ale także dla innych programistów, którzy mogą
być kiedyś zmuszeni do używania Twojego kodu. Styl składni powinien być spójny. Mamy doświadczenie
związane z XHTML-em, więc w tej książce zamykamy wszystkie tagi i używamy małych liter oraz znaków
cudzysłowu w atrybutach.
Zbierając razem to wszystko, o czym powyżej napisaliśmy, utwórzmy stronę startową w HTMLS
- taką jak pokazana na listingu 1 . 1 .
T o wszystko! Zapisz stronę jako plik .htm (lub .html) - od teraz możesz j uż rozpocząć wypełnianie
jej wspaniałą treścią.
28 Rozdział 1. Nowe e lementy stru kturalne w HTML5

Listing 1 . 1 . Prosta strona startowa w HTML5

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8" j>
< t i t l e>tytuf strony<jt i t l e>
<scri pt src = " my-j avascri pt-fi l e . j s " ><jscri pt>
<l i nk rel = " styl es heet " href="my-css -fi l e . cs s " j>
<jhead>
<body>
< 1-- nowe elementy HTML5 zostaną umieszczone tutaj ) _o>
<jbody>

Wskazówka
Wa l id acja jest bardzo użytecznym n arzędziem sprawdza n i a , d l aczego coś może n ie działać,
a wykorzystywanie jej w procesie rozwoju oprogramowa n i a jest n iezwykle korzystne . Pon ieważ jed nak
HTML5 jest nadal rozwij any, nie udostępnia żadnych oficj a l nych usług walidacyj nych . Walidator W3C ,
http://va/idator. w3.org/, s prawdza zgodność kodu z HTML5, ale ostrzega przy tym , że jest to fun kcj a
wyłącznie eksperymenta l n a . In nym wa lidatore m , którego możesz używać do testowan ia swoich stron ,
jest http://htm/5. va/idator.nuj. Warto testować swoje strony w o b u wa lidatorach .

Skąd pochodzą wszystkie nowe elementy?


Nowe elementy strukturalne zostały zaprojektowane, by informować przeglądarkę, jaką strukturę ma strona,
i by nadać treściom znaczenie (semantykę). Lecz skąd pochodzą ich nazwy?
W 2005 roku Google przeanalizowało ponad miliard stron internetowych, by dowiedzieć się, j akich nazw
klas używają programiści i twórcy witryn (http://code.google.com/webstats). Dało to ranowi Hicksonowi
(noszącemu pseudonim "Hixie"), redaktorowi głównej specyfIkacji HTML5, podstawę do rozmyślań na temat
nowych elementów. Mimo że było to pięć lat temu, czyli - biorąc pod uwagę rozwój internetu - dość dawno,
badanie odzwierciedla najważniejsze i najpowszechniej wykorzystywane w witrynach internetowych treści.
Poniższa lista zawiera 20 najpopularniejszych nazw klas używanych w tamtym czasie:
footer menu t i tl e
smal l text content
header nav copyri ght
button ma i n search
msonormal date smal l text
body styl el top
whi te l i nk

Chociaż kilka z tych nazw dotyczy prezentacji (na przykład wh ; te, s tyl el, mson orma l ), inne stanowią
elementy zawarte w specyfIkacji HTML5 (footer, nav, header).
Po co zatem używać tych nowych elementów? Cóż, HTML5 pozwala Ci nadać tworzonej treści znaczenie
(semantykę), więc jeśli na przykład Twoj a strona zawiera elementy nawigacji, możesz użyć elementu nav,
ponieważ ten element nadaje znaczenie swojej treści.
Wykorzystanie e lementu header do utworzen i a nagłówka witryny 29

PRZEPIS DLA POCZĄTKUJĄCYCH


Wykorzystanie elementu header
do utworzenia nagłówka witryny
Zacznijmy od górnej części typowej strony internetowej .
Element header j est często pierwszym fragmentem strony internetowej i zwykle to w j ego obrębie są
określone takie parametry strony jak logo, jej nazwa czy główne elementy nawigacji. Można go wykorzystać
wielokrotnie na tej samej stronie i, jak się przekonasz, może być użyty do poruszania się po określonej części
strony, nie tylko po całej witrynie. W elemencie header możesz umieścić na przykład formularz służący do
przeszukiwania witryny albo spis treści. Oto prosty przykład:
<header>
< i mg a l t = " HTML5 . Podręczni k program i sty - l ogo" src = " l ogo . png" j>
<h l><a href= " # ">HTML5 . Podręczni k program i sty<ja><j h l>
<jheader>

Jak mówi specyfikacja HTMLS, header może zawierać elementy pomagaj ące w nawigacji, więc zawartość
rysunku 1.2 mogła zostać określona przy użyciu elementu header, który obejmuje logo, główne łącza nawigacji
oraz formularz wyszukiwania. Mogłoby się także okazać, że musiałeś umieścić element nav poza elementem
header, co również jest dopuszczalne.

� Nazwa witryny Szukaj w witrynie

U Stronagłówna O witrvnie Zespół Wiadomości Kontakt

Rysunek 1 . 2 . Typowy element header zawierający tytuł witryny. logo oraz obszary
związane z wyszukiwaniem i nawigacją

Poniższa lista zawiera treści, które mogą się znajdować w elemencie header. Niektóre z nich są pokazane
na rysunku 1 .2:
• logo,
• nazwa/tytuł witryny,
• podtytuł witryny,
• formularz wyszukiwania,
• główne elementy nawigacji.

Nie istnieje ograniczenie do j ednego elementu header na stronie i nie musi się on znajdować na górze
strony. Jak wyjaśnimy szczegółowo później, jeśli Twoja strona zawiera kilka nagłówków, możesz rozważyć
ich umieszczenie w elemencie header. Możesz również użyć więcej niż jednego znacznika h l na stronie
i w rezultacie otrzymać stronę podobną do tej z listingu 1 .2 (o elemencie art; c l e przeczytasz w dalszej części
tego rozdziału) .
30 Rozdział 1. Nowe e lementy stru kturalne w HTML5

Listing 1 . 2 . Użycie wielu nagłówków na jednej stronie

<art i e l e>
<header>
<h l><a href= " # " >Rozdz i a ł 1 . <ja><jhl>
<p> 1 1 . 1 1 . 2 0 1 1<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas .
�Ves t i bul um tortor quam, feugi at v i tae, ul tr i e i es ege t , tempor s i t amet , ante . . . . <jp>
<jart i e l e>
<art i e l e>
<header>
<h l><a href= " # " >Rozdz i a ł 2 . <ja><jhl>
<p> 12 . 1 1 . 2 0 1 1<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas .
�Ves t i bul um tortor quam, feugi at v i tae, ul tr i e i es ege t , tempor s i t amet , ant e . <jp>
<jart i e l e>
<art i e l e>
<header>
<h l><a href= " # " >Rozdz i a ł 3 . <ja><jhl>
<p> 13 . 1 1 . 2 0 1 1<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas .
�Ves t i bul um tortor quam, feugi at v i tae, ul tr i e i es ege t , tempor s i t amet , ant e . <jp>
<jart i e l e>

Kod Z listingu 1 .2 wyświetla się w sposób pokazany na rysunku 1 .3.

Rozdział 1 .
1 1 . 1 1 .2 0 1 1

Pellentesque habitant morhi tristique senectus et netus e t ma1esuada fames a c turpis egestas . Vestibulum tortor
quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean
ultric ies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet es! et sapien ullamcorper pharetra. Vesribulum
erat \visi, condimenrum sed: conunodo vitae, omare sit amer: \:v1.si. Aenean fennentum, elit eget tinciclunt
condimentum, eros ipsum rutrom orei, sagittis tempus lacus enim ac duI. Danec non enim in turpis pulvinar facilisis.
Ut felis. Praesent dapibus, neque id cursus faucibus� tortor neque egestas augue. eu vulputate magna eros eu erat.
Aliquam erat volutpat. Nam clui mi, tinciclunt quis, accumsan pomitor, facilisis luctus, metus

Rozdział 2 .
12. 1 1 .20 1 1

Pellentesque habitant morbi tristique. senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
quam. feugiat vitae., ultricies eget, tempor sit amet, ante .

Rozdział 3.
1 3 . 1 1 .2 0 1 1

Pellentesque habitant morbi tristique. senectus et netus e t malesuada fames a c turpis ege stas . Vestibulum tortor
quam, feugiat vitae., ultricies eget, tempor sit amet, ante.

Rysunek 1.3. Wiele elementów header na jednej stronie (nie nadawano stylów)
Tworzenie n awigacj i przy użyciu e lementu n av 31

W obrębie elementu header mógłbyś umieścić również autora i datę, jednak specyfikacja HTMLS sugeruje,
że informację o autorze lepiej zamieścić w elemencie footer.
Jeśli w elemencie header znajduje się tylko jeden nagłówek (h l - h6), nie ma potrzeby, by używać tego
elementu; wystarczy samodzielny znacznik h l - h6.

PRZEPIS DLA POCZĄTKUJĄCYCH


Wykorzystanie elementu hgrou p
do gru powania nagłówków
Używaj ąc innego nowego elementu HTMLS, hgroup, możesz dodać do elementu header więcej informacji.
Element ten j est używany do grupowania więcej niż jednego powiązanego ze sobą nagłówka h l - h6.
Jeśli zatem Twoj a witryna ma podtytuł, mógłbyś użyć pokazanego na listingu 1 .3 elementu, który generuj e
układ z rysunku 1 .4. Chociaż element h group daje użyteczną możliwość związaną z grupowaniem, jest on
przeznaczony głównie do przekazywania do konspektu dokumentu (co omówimy później) informacji o tym,
który z nagłówków jest najważniejszy. N a listingu 1 .3 konspekt dokumentu pominie wszystkie nagłówki
z wyjątkiem najwyższego, w tym przypadku h L

Listing 1 . 3 . Wyłączanie wszystkich nagłówków z wyjątkiem h1

<header>
<hgroup>
<h l><a href= " # " >HTML5 . Podręczn i k program i s ty<ja><j hl>
<h2>Pyszne przepi sy HTML5<jh2>
<jhgroup>
<jheader>

HTML5. Podręcznik programisty Pyszne przepisy HTML5


Rysunek 1.4. Witryna zawierająca tytuł i podtytuł. Mogą się one znajdować
w elemencie hgroup

Na listingu 1 .3 treść elementu h2 nawiązuje do elementu h l, więc w tym przypadku możesz użyć hgroup.
Jeśli miałbyś pojedynczy nagłówek hl - h6, nie musiałbyś używać hgroup.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie nawigacji przy użyciu elementu nav
Element nav, jak możesz się spodziewać na podstawie j ego nazwy, ma związek z nawigacją. Jest używany do
umieszczania, na przykład w spisie treści, łączy do innych stron w ramach witryny lub do innych części strony.
Najbardziej powszechnym sposobem użycia elementu nav j est budowanie za jego pomocą głównego
mechanizmu nawigacji po witrynie. Typową praktyką j est wykorzystanie do zakodowania elementów
nawigacji nienumerowanej listy, j ak pokazano na listingu 1 .4.
32 Rozdział 1. Nowe e lementy stru kturalne w HTML5

Listing 1.4. Tradycyjny sposób kodowania elementów nawigacyjnych

<ul i d= " nav ">


<l i ><a href= " # " >Strona gfówna<ja><jl i >
<l i ><a href= " # ">O w i t ryn i e<ja><jl i >
<l i ><a href= " # ">Poznaj zespóf<ja><jl i >
<l i ><a href= " # ">Wi adomości <ja><jl i >
<l i ><a href= " # ">Konta kt<ja><jl i >
<ju l >

Gdy używamy elementów n a v w HTML5, k o d niewiele się zmienia. Kod dający efekt widoczny
na rysunku 1.5 jest mniej więcej taki jak pokazany na listingu 1 .5.

Listing 1 . 5 . Znaczniki nawigacyjne w HTML5

<nav>
<ul >
<l i ><a href= " # " >Strona gfówna<ja><jl i >
<l i ><a href= " # " >O w i t ryni e<ja><jl i >
<l i ><a href= " #" > Poznaj zespóf<ja><jl i >
<l i ><a href= " # " >Wi adomości <ja><jl i >
< l i ><a href= " #" > Konta kt<ja><jl i >
<jul >
</nav>

HTML5. Podręcznik programisty Pyszne przepisy HTML5


p - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - _ . _.. _ .. _ .. _--- - - --- _._ .. _ .. _ .. _ . _ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Strona główna O witrvnie Poznaj zespół \Viadomości Kontakt

._----------------------------------------------------------------------------------------------------------------------

Rysunek 1 . 5 . Szeroka na całą stronę treść związana z nawigacją,


znajdująca się wewnątrz elementu nav

J ak pokazano na listingu 1 .6, nav może się również znaj dować w elemencie h eader, ponieważ header
pozwala na umieszczanie w nim treści wprowadzającej i nawigacyjnej. Jednak treść ta nie musi się znajdować
w tym elemencie, a jej położenie może czasem zależeć od kwestii związanych ze stylami. Dość typową
praktyką jest również umieszczanie menu nawigacyjnego w stopce strony i duplikowanie w ten sposób
głównego mechanizmu nawigacji po witrynie.

Listing 1 . 6 . Element nav wewnątrz elementu header

<header>
<h l>Moja superwi tryna HTML5<jh l>
<nav>
<ul >
<l i ><a href= " # " >Strona gfówna<ja><jl i >
<l i ><a href= " # " >O w i t ryni e<ja><jl i >
<l i ><a href= " # " >Wi adomości <ja><jl i >
< l i ><a href= " #" > Konta kt<ja><jl i >
<jul >
<jnav>
<jheader>
Tworzenie n awigacj i przy użyciu e lementu n av 33

Umieszczanie wszystkich łączy ze strony w elemencie nav nie jest konieczne. Specyfikacja HTMLS ostrzega,
że za właściwe uważane jest tylko umieszczanie w elemencie nav bloków "głównej nawigacji".
W witrynach z wiadomościami lub blogami powszechnym widokiem jest boczny pasek zawierający łącza
do artykułów i innych stron. Znaczniki z listingu 1 .7 wykorzystano do uzyskania elementu z rysunku 1 .6.

Listing 1 . 7 . Wiele grup nawigacyjnych w pojedynczym elemencie nav

<nav>
<h2>Udostępni one<jh2>
<ul >
<l i ><a href= " # " >Pel l entesque hab i tant<ja><jl i >
<l i ><a href= " # " >Morbi tri s t i que senectus<ja><j l i >
<l i ><a href= " # " >Aenean u l t ri c i es mi v i tae est<ja><jl i >
<jul >
<h2>Przeczytane<jh2>
<ul >
<l i ><a href= " # " >Pel l entesque hab i tant<ja><jl i >
<l i ><a href= " # " >Morbi tri s t i que senectus<ja><j l i >
<l i ><a href= " # " >Aenean u l t ri c i es mi v i tae est<ja><jl i >
<jul >
<h2>ObejrzanejWystuchane<jh2>
<ul >
<l i ><a href= " # " >Pel l entesque hab i tant<ja><jl i >
<l i ><a href= " # " >Morbi tri s t i que senectus<ja><j l i >
<l i ><a href= " # " >Aenean u l t ri c i es mi v i tae est<ja><jl i >
<jul >
<jnav>

Pellentesque habitant morbi tristique s:enectus: et netus: et malesuada


fames ac twpis egestas. Ves-tibulum tortor quam., feugiat \itae, ułtric:ies.
l
dostępnione lPrzeczytanellObejrzane/W'ysluchanel
eget, tempor �it amet., ante. Donec eu libero sit amet quam. ege�tas
• Pellente5C!ue babitant
semper. Aenean ultriC-ies: mi \itae est. Mawis placemt eleife-nd leo.
• Morbi tristigue se.nectus
Quisque sit amet est et sapien ullamcorper pbaretra Vestibulum erat
wisi, condimentum sed, commodo \ltae, omare sit amet., \\isi. Aenean
• Aenean ultricies mi \itae est

fermentum, elit eget tincidunt condimentum, eros ipsum rutrom orci,


sagittis. tempus lacw; enim ac dili. Donec non enim in turpis puhinar
fzcilisis Ut feli$. Praesent dapibus, neque id CUI'SU5 faucibus, tortor
neque egestas augue, eu \:ulputate magna eros eu erat Aliquam erat
\"olutpat. �am dui mi, tincidunt quis, aCCUIllSan porttitor, facilisis
luctus, metru;

Rysunek 1 . 6 . Przykład pogrupowanych elementów nawigacji na pasku bocznym.


" Udostępnione", " Przeczytane" i " ObejrzanejWysłuchane" mogłyby się znajdować
w osobnych elementach nav

Zwróć uwagę na elementy h2, służące do oddzielenia grup łączy w elemencie nav. N a rysunku 1 .6
znaczniki h2 pełnią funkcję tytułów zakładek - po wybraniu tytułu przez użytkownika treść ulega zmianie
(ten efekt może być uzyskany za pomocą JavaScriptu). Nagłówek nie zawsze jest konieczny, ale jeśli tylko
to możliwe, powinien być używany do rozdzielenia i ustrukturyzowania grup nawigacyjnych. Z powodów
związanych ze stylizacją możesz być zmuszony do rozdzielenia poprzedniego przykładu na dwie struktury
nav, co również j est dopuszczalne.
Używanie elementu nav daje olbrzymie korzyści związane z dostępnością. Technologie ułatwień dostępu,
takie jak czytniki ekranu, będą w stanie wyszukać i natychmiast wykorzystać grupy nawigacyjne, zamiast
oczekiwać, aż pojawią się one na ekranie. Programiści umieszczali dotychczas na samym początku dokumentu
HTML łącza "skaczące" lub "pomijaj ące", które odwoływały się zwykle do głównego mechanizmu nawigacji
lub głównej treści. Jednak przy użyciu elementu nav będziesz mógł wkrótce opuszczać takie "pomijaj ące"
34 Rozdział 1. Nowe e lementy stru kturalne w HTML5

menu. Jedyny problem stanowi w tej chwili ograniczona obsługa elementów HTMLS w technologiach
wspomagających, powinno się to j ednak niebawem zmienić.

PRZEPIS DLA POCZĄTKUJĄCYCH


Użycie elementu article
Elementy art i e l e i seet i on (omawiany w następnym podrozdziale) są prawdopodobnie dwoma
najważniejszymi nowymi elementami strukturalnymi HTMLS, ale przy tym również dwoma spośród
najbardziej niejasnych.
Element art i e l e reprezentuje niezależną treść, która mogłaby istnieć samodzielnie i którą można
wykorzystywać wielokrotnie. Pomyśl o treści, którą widzisz w kanale RSS - j est ona prawie zawsze
oddzielnym artykułem. Nawet po przeniesieniu z kanału w pełni zachowałaby sens.
Specyfikacja HTMLS sugeruje kilka przykładów wykorzystania elementu art i e l e, takich j ak wpis
na forum, artykuł w magazynie lub gazecie, wpis na blogu albo komentarz wpisywany przez użytkownika.
Listing 1 . 8 wykorzystuj e element art i e l e do oznaczenia pojedynczej wiadomości, co pokazano
na rysunku 1 .7.

Listing 1.8. Oznaczanie wiadomości elementem artic/e

<arti c I e>
<header>
<h l>HTML5 oszczędza mi l i ony ! <jhl>
<jheader>
<p><strong>Pel l entesque hab i t ant morbi t r i s t i que<jstrong> senectus et netus et mal esuada fames ac turp i s
�egestas . Vest i bu l um tortor quam, feugi at vi tae , u l t ri c i es eget ...<jp>
<h2>! nny tytuf<jh2>
<ol >
<l i >Lorem i psum dol or s i t ame t , consectetuer adi p i s c i ng el i t . <jl i >
<l i >Al i quam t i nc i dunt maur i s e u r i s us . <jl i >
<jol >
<bl oc kquote><p>Lorem i psum dol or s i t amet , consectetur ad i p i sc i ng el i t . V i vamus magna. Cras i n m i a t fel i s
�al i quet congu e . Ut a est eget l i gul a mol est i e gra v i da . ...<p><jbl oc kquote>
<h3>! j eszcze i nny tytuf<jh3>
<ul >
<l i >Lorem i psum dol or s i t ame t , consectetuer adi p i s c i ng el i t . <jl i >
<l i >Al i quam t i nc i dunt maur i s e u r i s us . <jl i >
<jul >
<p>Ten artykuf j est autorstwa Toma Leadbettera zostaf opubl i kowany w " Czas i e HTML5 " w n i edz i e l ę ,
�32 l i stopada 2 0 1 0 . <jp>
</art i c l e>

Powyższa notka została umieszczona w elemencie a rt i e l e, ponieważ stanowi zamkniętą treść. Czy
pojawiłaby się w syndykacji (czyli kanale RSS)? Tak! Czy samodzielnie ma sens? Tak! A zatem jest to art i e l e.
Jak odkryjesz w późniejszych rozdziałach, możesz zagnieździć element seet i on w elemencie art i e l e,
a element art i el e wewnątrz elementu seet i on .
Specyfikacj a HTMLS mówi, że art i e l e jest niezależnym fragmentem treści i nawet komentarze na blogu
mogą być elementami art i e l e.
Gru powanie treści przy użyciu e lementu section 35

HTML5 oszczędza miliony!


Pellentesque habitant morbi tristique senectus et netus e t malesuada fames a c turpis egestas. Vestibulum tortor quam�
feugiat vitae, ultricies eget, tempor sit amet� ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi
vitae est. Mauris placerat eleifend leo. Quisque sit arnet est et sapien ullarncorper ph aretra. Vestibulum erat wisi.
condimentum sed, cornmodo vi'tae, omare sit amet, wisi. Aenean fermentum: elit eget tincidunt condirnentum, eros
ipsum rutrum orci, sagittis tempus lacus e.nim ac dui. Donec non enim in turpis pulvinar facil is is . Ut felis.

Inny tytuł

L Lorem ipsum dolor sit amet, consecteruer adipiscing elit.


2. Aliquarn tincidunt mauris eu risus.

Lorem ipsum dolor sit arnet, consectetur adipiscmg elit. Vivamus magna. Cras in mi at felis aliquet congue.
Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifeud. libero at sagitis molis, tellus est
malesuada tellus, at luctus turpis elit sit arnet quarn. Vivarnus pretium ornare est.

I jeszcze inny tytuł


Lore.rn ipsum do lor sit arnet. consectetuer adipiscing elit.
Aliquam tincidunt mauris eu risus.

Ten artykuł jest autorstwa Toma Leadbettera i został opublikowany \V "C zasie HTML5" w niedzielę. 32 listopada 20 10.
Rysunek 1 . 7 . Prosty element article z treścią (bez nadawania stylów)

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Gru powanie treści przy użyciu elementu section
Element sect ; on j est częścią treści lub strony, która prawie zawsze wymaga tytułu. Może być użyty do
zgrupowania treści podrozdziału lub punktu i być podzielony na kolejne podpunkty, jeśli jest to wymagane.
Nie należy go stosować jako ogólnego opakowania dla celów związanych ze stylizacją. Element sect; on
może zawierać elementy art ; c l e, a elementy art ; c l e mogą zawierać treść podzieloną na elementy sect ; on.
Musisz się zatem zastanowić nad tym, kiedy używać art ; c l e, a kiedy sect; on. Listing 1.9 jest przykładem
tego, kiedy należy używać elementu sect ; on, co pokazano na rysunku 1 .8.

Listing 1 . 9 . Utworzenie prostej strony z różnymi rodzajami wiadomości umieszczonymi w elementach section

<hl>Mnóstwo wi adomośc i <j h l>


<section>
<h l>W i adomośc i sportowe<jhl>
<p>Tutaj umi eś c i my wi adomości sportowe . <jp>
</section>
<section>
<h l>W i adomośc i rozrywkowe<jhl>
<p>W i adomości rozrywkowe zostaną umi eszczone tutaj . <jp>
</section>
<section>
<h l>W i adomośc i d l a man i a ków komputerowyc h<jhl>
<p>W i adomości dl a man i a ków komputerowych zostaną umi eszczone w tej częśc i strony. <jp>
</section>
36 Rozdział 1. Nowe e lementy stru kturalne w HTML5

Mnóstwo wiadomości

Wiadomości sportowe
Tutaj umieścimy \"iadomości sporto\�:e_

Wiadomości rozrywkowe
\Viadomości rozrywkowe zostaną u
mieszczone tutaj.

I
Wiadomości dla maniaków komputerowych
\Viadomości dla maniaków komputerowych zostaną umieszczone w tej części strony.

Rysunek 1.8. Prosta strona z wiadomościami z zaznaczonymi elementami section

Na rysunku 1 .8 każdy fragment objęty elementem sect ; on ma swój własny nagłówek i każdy z nich jest
całkowicie oddzielony od pozostałych. Gdyby na stronie pojawiła się inna treść, mógłbyś opakować całość
w jeden element sect ; on i nadać mu tytuł "Rodzaje wiadomości, które pokazujemy":
<section>
<h l>Rodzaj e wi adomośc i , które pokazuj emy<jh2>
<section>
<h l>W i adomośc i rozrywkowe<jhl>
<p>W i adomości rozrywkowe zostaną umi eszczone tutaj . <jp>
</section>

</section>

Dodatkowo mógłbyś podzielić punkt "Wiadomości dla maniaków komputerowych" na kolejne podpunkty:
<section>
<h l>Rodzaj e wi adomośc i , które pokazuj emy<jh2>
<section>
<h l>W i adomośc i d l a man i a ków komputerowyc h<jhl>
<p>W i adomości dl a man i a ków komputerowych zostaną umi eszczone w tej częśc i strony. <jp>
<section>
<h2>W i adomośc i o grach<jh2>

</section>
<section>
<h2>W i adomośc i o gadżetach<jh2>

</section>
</section>

</section>

Którego elementu powinieneś używać: article czy section?


Elementu sect ; on używa się podobnie jak znacznika d ; v, ale w przeciwieństwie do tego znacznika, sect ; on
posiada znaczenie (semantykę) - grupuje powiązane ze sobą treści.
Wewnątrz elementu sect ; on mogą być umieszczone elementy art ; c l e. Rozważmy przykład strony
z wiadomościami. Może ona mieć część zawieraj ącą wiadomości oraz - w j ej ramach - różne kategorie
wiadomości.
Tworzen ie paska bocznego za pomocą elementu aside 37

Mógłbyś zamieścić nagłówek "Wiadomości", a pod nim, niczym w gazecie, rozmaite rodzaje wiadomości.
W HTML 4 opakowałbyś całość elementem d i v, ale teraz możesz wykorzystać element sect i on. Każdy rodzaj
wiadomości byłby wtedy w swoim własnym elemencie sect i on i miałby swój własny tytuł.
Jeśli uważasz, że dana treść stanowi zamkniętą całość, powinieneś użyć elementu art i c l e.

Wskazówka
J a k przekonasz się w d a lszej części książki, kiedy będziemy mówić o tworzen i u konspektu w HTML5,
ważne jest sprawdzenie, czy użyłeś prawidłowego znaczn ika.
Często popełn i a n ą n a wczesnym eta pie n a u k i HTML5 pomyłką jest używanie elementu s ect i on do
opakowywania całych stron, na przykład tak: <section c l ass= "conta i ner"> albo <sect i on cl ass= "wrap">.
Nie jest to prawidłowy sposób użycia e lementu sect i on.
J a k mówi s pecyfi kacj a HTML5:

" Zaleca się stanowczo, aby autorzy postrzeg a l i użycie elementu d i v j a ko rozwiązanie ostateczne,
m ające zastosowan ie w sytu acj i , gdy żaden inny element nie jest właściwy. Używanie elementu d i v
zam i ast elementów bardziej o d powied n ich s kutkuje utru d n i e n i a m i w przegląd a n i u strony przez
użytkown i ków oraz dokonywa n i u zmian przez autorów " .
Element d i v " nie ma żadnego s pecj a lnego znacze n i a " , więc jest wykorzystywany do grupowa n i a treści,
która n ie pasuje do żadnego z nowych elementów HTML5. Często potrzebny jest do styl izacj i , w sytuacj i
kiedy C S S n i e d aje możl iwości wyod ręb n i e n i a danej treści w żaden i n n y s posób . J eżel i używasz
elementu sect i on, zostanie on uznany za zn aczący d l a dokumentu i z tego powodu dodany do jego
kon s pe ktu , n atom i ast e lement d i v n i e jest tam dod awany. J e ś l i zatem używasz elem entu sect i on
ze względu na style, zam iast n iego powin ieneś używać elementu d i v.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie paska bocznego za pomocą elementu aside
Element as i de jest przeznaczony do grupowania zawartości luźno powiązanej z otaczaj ącą treścią, takiej
jak lista najbardziej popularnych wpisów, kategorie bloga albo ostatnie komentarze. Ten rodzaj treści jest
związany z główną zawartością strony, ale przy tym pozostaje od niej oddzielony.
Obecnie, tworząc stronę internetową, powszechnie umieszcza się na niej "pasek boczny". Nie musi
on się fizycznie znajdować w bocznej części strony, natomiast zwykle zawiera takie elementy jak powiązane
łącza lub lista kategorii. Poprawne użycie elementu as i de zależy od tego, gdzie go umieścisz. Jeśli znajduje
się on wewnątrz elementu a rt i c l e, jego treść powinna nawiązywać do zawartości znacznika a rt i c l e, tak
jak słowniczek. Jeśli jednak a s i de znajduje się poza elementem art i c l e lub sect i on, jego treść powinna być
powiązana z zawartością strony, tak jak powiązane łącza witryny, kanał Twittera właściciela albo odwołujące
się do tematyki witryny reklamy. Listing 1 . 1 0 objaśnia, jak utworzyć blok "powiązanych łączy", taki j ak
przedstawiony na rysunku 1 .9.

Listing 1 . 10 . Użycie elementu aside d o utworzenia paska bocznego "Powiązane łącza "

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< t i t l e>Ta strona ma tadny konspe kt<jt i t l e>
38 Rozdział 1. Nowe e lementy stru kturalne w HTML5

<styl e>
art i c l e , as i d e , nav { d i s p l ay: bl oc k ; }
art i c l e , as i de { fl oa t : l eft ; }
art i c l e { w i d t h : 500px ; }
nav { w i d t h : 250px ; }
</styl e>
</head>
<body>
<art i c l e>
<header>
<hl>10 i nformacj i na temat HTML5</hl>
</header>
<p><strong>Pel l entesque hab i t ant morbi t r i s t i que</strong> _</p>

</art i c l e>
<asi de>
<h2>Powi ązane tącza</h2>
<nav>
<ul >
<l i ><a href= " #" > lO i nformacj i n a temat HTML 4</a></l i >
< l i ><a href= " #" > lO i nformacj i n a temat CSS3</a></1 i >
<l i ><a href= " #" > lO i nformacj i n a temat JavaScri ptu</a></l i >
<fu l >
</nav>
</asi de>
</body>
</html >

1 0 informacj i na temat HTML5 Powiązane łącza

10 informacji na temat HTML 4


PeUente'que b a bitont morbi tristique senectus et netus et malesuada farnes 10 informacji na temat CSS3
ac turpis eg�stas_ \ estibulum tortoc quam, feugiat "itae. ultricies eget
10 informacji na temat

tempor sit amet� ante. Donec eu libero sit amet quam egestas sempet". Aenean
Ja" aScripto
ulrricies mi vitae est. Mauris placemt e1eifend leo. Quisque sit amet est et
sapien ullarncorper pharetra. Ve'tibulum erat wisi, condimentum ,ed,
eomm.odo vi t. a � . omare sit amet, wisi_ Aenean f�enrum. elit egeI tincidunt
condime.nrum, eros ipsum rutrom ocei, saginis tempus lacus enim ac dui.
Donec non enim in torpis puh'inar faeilisis. Ut felis.

Inny tytuł

l . Lorem ipsum dolor sit amet, consecteruer adipiscing elit.


2. Aliquam tmcidunt mauris cu risus.

Lorem ipsum dolor sit amet. consectetur adipiscing elit. Vi"amus


magna. Cras in mi at relis aliquet eongue. Ut a est eget ligula
molestie grayida. Curabitur massa. Donec eleifend, libero at
sagitis molis, tellus eSl malesuada tellus, at luctus turpis elit sit
amet quam. Vi\"amus pretium ornare es!.

I jeszcze inny tytuł

Lorem ipsum dolor sit arnet. eonseetetuer adipiseing elit.


Aliquam tincidunt mauris eu risus.

Ten artykuł jest autorstwa Toma Leadbettera i został opublikowany w


.,Czasie HTML5" w niedziel�. 32 listopada 2010.

Rysunek 1.9. Prosty układ strony zawierającej pasek boczny


Użycie e lementu footer 39

Możesz również zagnieździć element a s ; de wewnątrz innych elementów, włącznie z elementem art ; e l e.
Rozszerzając poprzedni przykład, mógłbyś udostępnić użytkownikowi słowniczek zawierający wyjaśnienie
użytych w zasadniczej treści słów lub zwrotów, które mogą nie być mu znane:
<art i c l e>
<header>
<hl>10 i nformacj i na temat HTML5<jh l>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas .
�Ves t i bul um tortor quam, feugi at v i tae , ul tr i c i es eget , tempor s i t amet , ant e . Donec eu l i bero s i t
�amet quam egestas semper. Aenean ul tri c i es m i vi tae e s t . Maur i s pl acerat el ei fend l eo . <jp>

<jheader>
<asi de>
<h2>Stowni czek<jh2>
<p>Prawdopodobn i e zam i eśc i l i śmy na tej stron i e mnóstwo a kroni mów s krótów, warto w i ęc dodać
�stown i cz e k . <jp>

</asi de>
<jart i c l e>

PRZEPIS DLA POCZĄTKUJĄCYCH


Użycie elementu footer
Element footer, jak wskazuje jego nazwa!, znajduje się zwykle (chociaż nie zawsze) na dole strony. Element
ten zawiera treść dotyczącą elementu, w którym się znajduje, włączaj ąc w to informacj e o autorze lub
właścicielu witryny, prawach autorskich i j ej regulaminie. Jeśli znajduje się wewnątrz artykułu lub j ego
fragmentu, może zawierać datę publikacji artykułu, tagi, kategorie i inne metadane.
Specyfikacja HTMLS sugeruje następujące rozwiązanie bardzo typowego zagadnienia - dodanie
informacji o prawach autorskich zamieszczonej na stronie:
<footer>
<smal l >&copy; Copyri ght HTML5 . Podręczni k programi sty 2012<jsmal l >
<jfooter>

Powyższy przykład znajdowałby się prawdopodobnie zaraz przed zamykaj ącym tagiem </body>.
(Zwróć również uwagę na to, że informacj a dotycząca praw autorskich znaj duje się w tagu sma 1 1 .
Powrócimy do tego w następnym rozdziale) .
Tak j ak w przypadku elementu header, możesz użyć elementu footer więcej niż raz na stronie. Możesz
również umieścić go na przykład wewnątrz artykułu. Na listingu 1 . 1 1 w ramach elementu art ; el e zostało
zagnieżdżonych kilka elementów footer, co ilustruje rysunek 1 . 1 0 .

Listing 1 . 1 1 . Strona z kombinacją elementów artic/e i footer

<art i c l e>

<hl>10 i nformacj i na temat HTML5<jh l>

<footer>

! Ang.footer - "stopka" - przyp. tłum.


40 Rozdział 1. Nowe e lementy stru kturalne w HTML5

<p>Ta wi adomość zostafa opubl i kowana <t i me>l kwi et n i a 2 0 1 1<jt i me> przez <a href= " # " >Toma
�Leadbettera<ja><jp>
</ footer>
<p><strong>Pel l entesque hab i t ant morbi t r i s t i que<jstrong> _<jp>
< 1-- treść strony _o>
<footer>
<p>Ta wi adomość zostafa opubl i kowana < t i me> l kwi etn i a 201 1<jt i me> przez <a href= " # " >Toma
�Leadbettera<ja><jp>
<a href= " # " >Przeczytaj następny artykuf Toma<ja>
</ footer>
<jart i c l e>
< footer>
<smal l >&copy; Copyri ght Podręczn i k HTML5 201 1<jsmal l >
</footer>

10 informacji na temat HTML5

a wiadomość została opublikowana l kwietnia 201 1 przez Toma Leadbenera

Pellente.sque babitant morbi tristique senectus et netus et ma1esuada farnes ae turpis egestas. Vestibulum tortor quam. feugiat ,:iłae,
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. A�nMn ultricies mi virae esr. Mauris placer"" el�ifend
l�. Quisque sit aroet est et sapien ullamco�r phatetra.. Vestibulum erat wisi, condimentum std, cc:n.-nodo ·�lu�, ornate sit amet. wisi.
Aenean fennentum, elit eget tincidunt condimentum. eros ipsum rutrom orci. saginis tempus lacus enim ae dui. Dente non enun in rurpis
puh-inar facilisis. Ut felis.

Inny tytuł
l. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
2. Aliquam tincidunt mauris eu risus.

Lorem ipsum dolor sit amet, consectetur adipiscmg elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabirur massa. Denec eleifend, Libero at sagiris molis, teHus est malesuada lelIas, at luctus rurpis elit sit
amet quam. Vi\"amus pretium omare est.

I jeszcze inny tytol


• Lorem ipsum dolor sit amet, consectetuer adipiscmg elit.
• Aliquam tincidunt mauns eu risus.

Ten artykuł jest autorstwa Toma Leadberttra i został opublikowany w "Czasie H1ML5" w niedzielę, 32 listopada 2010.

a wiadomość została opublikowana 1 kwietnia 201 1 przez Toma Leadbenera

rzeczvta' nast nv artvkuł Toma

Co Co Ti.ght Podr�cznik HThn.5 201 1

Rysunek 1 . 10. Układ strony z wieloma elementami {ooter

W tym przykładzie w elemencie art i e l e zostały umieszczone dwa elementy footer. Wyświetlanie autora
lub daty na górze i dole pojedynczej wiadomości lub wpisu na blogu to typowa praktyka, a elementu footer
możesz używać tyle razy, ile chcesz.
W poprzednim przykładzie wprowadziliśmy element t i me, o którym opowiemy w następnym rozdziale.
Możesz umieścić w elemencie footer inne treści, dotyczące przykładowo nawigacji (oczywiście
z wykorzystaniem elementu nav ) , logo partnerów i umów licencyjnych. Możesz także często zobaczyć
tekst w rodzaju: "Ta witryna jest obsługiwana przez <nazwa systemu zarzqdzania treściq>".
Specyfikacja HTMLS mówi, że element footer może zawierać łącza do powiązanych dokumentów,
i chociaż wcześniej używałeś w tym celu kombinacji elementów as i de i nav, możesz również użyć do takiej
treści elementu footer, jeśli tylko znajduje się on wewnątrz elementu art i e l e. Element footer może zawierać
na przykład łącza do poprzedniego i następnego artykułu, co wyglądałoby mniej więcej tak:
Wykorzystanie narzędzia HTML5 Outliner 41

<art i c l e>
. . . cata treść tego artykutu . . .
<footer>
<a href= " # " >Poprzedni artykut<ja> I <a href= " # " >Następny artykut<ja>
<jfooter>
<jart i c l e>

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Wykorzystanie narzędzia HTML5 Outliner
do utworzenia prawidłowej struktury dokumentu
Dzięki nowym elementom HTMLS możesz sprawić, by Twoja treść układała się w logiczny sposób,
a użytkownicy poruszali się po niej (na przykład z wykorzystaniem czytników ekranu) za pomocą hierarchii
przypominającej spis treści. Testowanie konspektu pozwala Ci sprawdzić, czy poprawnie używasz nagłówków
i elementów sect ; on. Do Twojej dyspozycji pozostają rozmaite rozszerzenia przeglądarek i witryny, ale tutaj
skupimy się na rozszerzeniu Google Chrome: http://code.google.com/p/h50/.
Pobierz rozszerzenie Chrome. Po j ego zainstalowaniu na pasku adresu poj awi się ikona - taka, j aką
pokazano na rysunku 1 . 1 1 .

T Tom Leadbetler I A freelanc

Rysunek 1.11. Witryna z ikoną rozszerzenia HTML5 Outliner w Google Chrome

Kiedy wybierzesz to narzędzie, zostaną wyświetlone dane przypominające wyglądem spis treści, zwykle
z wcięciami.
Jeśli właściwie zorganizowałeś treść strony, spis treści powinien być ustrukturyzowany i logiczny.
Komunikat "Untitled section/article" j est niepożądany - j eśli został wyświetlony, być może użyłeś
nieprawidłowych znaczników, musisz zatem ponownie się im przyjrzeć. Zauważ jednak, że w przypadku
nav i a s ; de komunikat " Untitled section" j est jak najbardziej dozwolony.
Poprawny konspekt mógłby wyglądać następująco:
1. Nazwa witryny
a. Blog
i. Tytuł artykułu
ii. Tytuł artykułu
b. O mnie
i. Moje imię i nazwisko
ii. Rzeczy, które lubię
iii. Rzeczy, których nie lubię
c. Kontakt
42 Rozdział 1. Nowe e lementy stru kturalne w HTML5

Rysunek 1 . 1 2 pokazuj e przykład konspektu dokumentu. Wcięcia są prawidłowe i nie ma


niezatytułowanych punktów (poza elementem nav, ale to dopuszczalne).

Cl Poprowny konspekt

c � Cl localhos konspel<t.html 'ti -

1 . Mnóst'....o WiadomoścI
1. UnHtled NAV
Mnóstwo " 2. :lli!!!
3. Rozrywka
I 4. Ko moutery

Dostarczamy er O"J ' "LiO .....J '>L� "" o ' T" U " U I " "",, ' .
� ----------'
-

• Strona główna
. �
• Kontakt
• StronągłÓ\\Jlą

Sport

Rozrywka

Komputery
Rysunek 1. 12. Konspekt prostego dokumentu HTML5 w Google Chrome

Konspekt, który utworzysz w tym przepisie, wygląda następująco:


1. Mnóstwo wiadomości
1 . Untitled NAV
2. Sport
3. Rozrywka
4. Komputery

Listing 1 . 1 2 pokazuje kod źródłowy tej strony.

Listing 1 . 1 2 . Tworzenie prostego konspektu dokumentu

<header>
<hgroup>
<h l><a href= " # " >Mn6stwo wi adomości </a></h l>
<h2>Dostarczamy Ci najr6żn i ej szych wi adomośc i ! </h2>
</hgroup>
</header>
<nav>
<ul >
<l i ><a href= " # " >Strona gł6wna</a></l i >
<l i ><a href= " # " >O w i t ryni e</a></l i >
< l i ><a href= " #" > Konta kt</a></l i >
<l i ><a href= " # " >Strona gł6wna</a></l i >
<fu l >
</nav>
Tworzenie strony z wiadomościami 43

<sect i on>
<h 1>Sport<j h1>
<jsec t i on>
<sect i on>
<h1>Rozrywka<jh1>
<jsec t i on>
<sect i on>
<h1>Komputery<jh1>
<jsec t i on>

Strona zawiera na początku element h eader, użyty jako pierwszy węzeł konspektu (nie tytuł strony),
a następnie elementy seet ; on, które zawierają nagłówki. W elemencie header znajduje się element hgroup
z elementem h2 zawierającym tekst: "Dostarczamy Ci najróżniej szych wiadomościl" , ale nie widzisz go
w konspekcie, ponieważ konspekt czyta wyłącznie pierwszy nagłówek (hl, h2, h3, h4, h5 lub h6) w danym
elemencie.
Elementy seet ; on, art ; el e, nav i a s ; de rozpoczynają kolejne poziomy wcięć (podpunkty) w konspekcie.
Elementy seet; on zawieraj ą nagłówki h l , których treść j est wyświetlana w konspekcie. Mógłbyś tu użyć
elementów h2 albo h3, gdybyś chciał; to nie ma znaczenia. Jeśli element seet ; on zawierałby treść, ale nie
nagłówek, w konspekcie byłby wyświetlany tekst "Untitled section", czego powinieneś unikać.

PRZEPIS DLA ZAAWANSOWANYCH


Wykorzystanie wszystkich nowych elementów
do utworzenia strony z wiadomościami
Rysunek 1 . 1 3 oraz kod z listingu 1 . 1 3 ilustruj ą wykorzystanie wszystkich nowych elementów HTMLS
do utworzenia układu strony z wiadomościami. Znajdują się tu pewne podstawowe instrukcje CSS służące
do spozycjonowania elementów, ale nie jest to jeszcze nic skomplikowanego - takimi rzeczami zajmiemy
się dopiero w jednym z późniejszych rozdziałów.

Listing 1 . 13 . Tworzenie strony domowej z wiadomościami

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< t i t l e>Mnóstwo wi adomoś c i - naj l epsza w i t ryna z wi adomośc i ami , j a ka ki edyko l w i e k i st n i a ta<jt i t l e>
<styl e>
header, nav , sec t i on , art i c l e , footer { d i spl ay : b l oc k ; }
header, nav { border-bottom: 1px dotted #000 ; c l ear: bot h ; w i dt h : 100%; }
nav l i { d i spl ay: i n l i ne ; }
secti on#headl i ne { c l ear: bot h ; border: 5px sol i d #000; padd i ng : 1%; w i d t h : 97% ; }
secti on#sport s , secti on#entertai nment , sect i on#nerdy { fl oat : l eft ; marg i n : O 5px;
padd i ng : 1% ; w i d t h : 30%; }
as i de , footer { c l ear: bot h ; }
as i de img { border: 1px sol i d #cc c ; marg i n : O 10px O O ; }
<jstyl e>
<jhead>
<body>
44 Rozdział 1. Nowe e lementy stru kturalne w HTML5

<header>
<hgroup>
<h l>Mnóstwo wi adomośc i <jhl>
<h2>Dostarczamy Ci najróżn i ej szych wi adomośc i ! <jh2>
<jhgroup>
<jheader>
<nav>
<ul >
<l i ><a href= " # " >Strona główna<ja><jl i >
<l i ><a href= " # " >Sport<ja><j l i >
<l i ><a href= " #" > Rozrywka<ja><jl i >
<l i ><a href= " #" > Komputery<ja><jl i >
<l i ><a href= " # " >O w i t ryni e<ja><jl i >
< l i ><a href= " #" > Konta kt<ja><jl i >
<jul >
<jnav>
<sect i on i d= " headl i ne " >
<hl>Główny artykuł<jhl>
<art i c l e>
<header>
<h2><a href= " # " >To j est nasz naj ważni ejszy artykuł<ja><jh2>
<p>lO l i stopada 20 10<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas . <jp>
<jart i cl e>
<jsec t i on>
<sect i on i d= " sports " >
<h l>Sport<j hl>
<art i c l e> < 1-- (x3) _ o >
<header>
<h2><a href= " # " >Sport - tytuł 1<ja><jh2>
<p>lO l i stopada 20 10<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas . <jp>
<jart i cl e>
<jsec t i on>
<sect i on i d= " entertai nment" >
<hl>Rozrywka<jhl>
<art i c l e> < 1-- (x3) _ o >
<header>
<h2><a href= " #" > Rozrywka - tytuł 1<ja><jh2>
<p>lO l i stopada 20 10<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas . <jp>
<jart i cl e>
<jsec t i on>
<sect i on i d= " nerdy" >
<hl>Komputery<jhl>
<art i c l e> < 1-- (x3) _ o >
<header>
<h2><a href= " #" > Komputery - tytuł 1<ja><jh2>
<p>lO l i stopada 20 10<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas . <jp>
<jart i cl e>
<jsec t i on>
<as i de>
<a href= " # " >< i mg al t = " Wytworna rekl ama" src = " i magesjsnazzy-advert . g i f" hei ght= " 128"
�wi dth= " 128" j><ja>< I -- (x4) _ o >
Tworzenie strony z wiadomościami 45

<jasi de>
<footer>
<p>W i t ryna używa <a href= " # ">systemu zarządzan i a treśc i ą bez nazwy<ja><jp>
<p>W i t ryna znajduj e s i ę na <a href= " # " >serwerze bez nazwy<ja><jp>
<p>Wszys t k i e zdj ęc i a w tej wi tryni e są wtasnoś c i ą fotografa<jp>
<smal l >&copy; Copyri ght N i kt 201 1<jsmal l >
<jfooter>
<jbody>
<jhtml >

"Inó two wiadorności

DOSI�l'czallly Ci n�irMn iej, zyc h wiadom d�

'.

J(llln,�lQ]O

Sporl Rou}'wka Koml,ut.ry

porl - W l u l 1 Roz n", ka - Mul i KOhlputtrv - .ylul l


10 łm(lJloldł, lQ10 IO ]Ił1� lO IO IO IIJI��OIO

Pt.ll� lubltMł iBOfto:ł tf� hll� ilubllili!!ł � lłfńl.qęt Pelltetoetq'Dt lu.I:hlld �bI�.ąue
�nLIi -et l\d'Iri; e1 fnt!ie-.wb: 6.mn �rw d � C'1 n.l!kalUd..l
i l t'un...:. 1inlIt:Cru.t tt ",IUI et mJi)uu.łd..i: tłdn

II: mrptJ t:J61B- a.:: rurpu. �lM.. 1C 1UfIU e:J'61a:L

[l0rl - IYluł 2 Rozrywka. - tytuł 2 KODlllU t uy- l)'tul 2


ł lail��Ott)

�lh:n..�� b"J,lj � Q� ,1[1I�� łHl;Ill_ � linJ(� �lkW� bl.l:Jłt.il .,;r'b1 1m;tqPt:


młlnv.lda łunoe.: IC!I«M n MUl et IUlJHIJłda flilll'!' tcM(M H IIeOJ . n � f1mlH
up''- ętnb;l. :llĆ h�f', r;t'r'Jm
�' 'fI O!omłf �
c a:1haf'L'L .;tc".U'I;

'porl - Muł 3 Rozn'wka - tYluł 3 KomJlut.ry - I)'tul 3


8 1lJlopad.;.JOI'O 8 h�JI)IO S I���IO

Pcll� lb.t:łnDl!t: morb:a � �1I� iblbIlAll!l I!lilOCbi Iłfn.ł� PeIl� łWtttllfll łlllOtbJ �tqoe:
�hlIi -ci: ftdV6 et mde.� llmri �fl.ljd l5d"llło � f� tliBt:łi �W:i d �Ul d ll1*1a --uad;t {ła:k:łi
IIC tulptJ tf61a. "' ....... -- 1IC 1UfPH *ł1ti..

wytworna wytworna wytworna wytworna


reklama reklama ,reklama rek.lama

Rysunek 1 . 13. Układ strony z wiadomościami utworzonej z wykorzystaniem nowych elementów HTML5

Teraz, kiedy uporałeś się z podstawowym układem i kodem, powinieneś sprawdzić konspekt dokumentu.
Na podstawie powyższego kodu zostanie utworzony następujący konspekt:
46 Rozdział 1. Nowe e lementy stru kturalne w HTML5

1 . Mnóstwo wiadomości
a. Untitled NAV
2. Główny artykuł
a. To jest nasz najważniejszy artykuł
3. Sport
a. Sport - tytuł 1
b. Sport - tytuł 2
c. Sport - tytuł 3
4. Rozrywka
a. Rozrywka - tytuł 1
b. Rozrywka - tytuł 2
c. Rozrywka - tytuł 3
5. Komputery
a. Komputery - tytuł 1
b. Komputery - tytuł 2
c. Komputery - tytuł 3
6. Untitled ASIDE

Wygląda to wspaniale! W przeciwieństwie do elementów sect ; on, art ; c l e, nav i as ; de, element footer
nie dzieli treści na fragmenty, więc nie pojawia się w konspekcie. Elementy nav i a s ; de nie są zatytułowane,
ale to nie problem. Gdybyś chciał, mógłbyś nadać elementowi a s ; de tytuł, jednak czy reklama produktu
zewnętrznego zasługuj e na tytuł?

PRZEPIS DLA ZAAWANSOWANYCH


Wykorzystanie wszystkich nowych elementów
do utworzenia strony z wynikami wyszukiwania
Na listingu 1 . 14 wykorzystasz kilka nowych elementów HTML5 do utworzenia struktury strony z rezultatami
wyszukiwania (rysunek 1 . 14). Pamiętaj, że w tym przepisie nie ma CSS-a, a jedynie HTML.

Listing 1 . 14. Strona z wynikami wyszukiwania zbudowana z wykorzystaniem omówionych elementów

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< t i t l e>Wys z u k i wani e<jt i t l e>
<jhead>
<body>
<nav>
<ul >
< l i >S i eć<jl i >
Tworzenie strony z wyn i kam i wyszu kiwania 47

<l i ><a href= " # " >Zdj ęc i a<ja><jl i >


<l i ><a href= " # " >Wi deo<ja><jl i >
<l i ><a href= " # " >Mapy<ja><jl i >
< 1-- etc. -- >
<jul >
<jnav>

<header>
<h 1>Szukan i e nazwy przedsi ębi orstwa<jh 1>
<jheader>

<form>
<fi el dset>
<l egend>Szukaj<jl egend>
<l abe l for= " search i nput " >Szukaj<jl abe l >
<i nput type=" searc h " i d = " searc h i nput" name= " searc hi nput" j>
<i nput type=" submi t " j>
<a href= " # " >Szukan i e zaawansowane<ja>
<jf i e l dset>
<jform>

<nav>
<h2>Doktadne szukani e<jh2>
<ul >
<l i ><a href= " # " >Wszyst ko<ja><jl i >
<l i ><a href= " # " >Wi adomości <ja><jl i >
<l i ><a href= " # " >Wi ęcej<ja><jl i >
<jul >
<h3>S i eć<jh3>
<ul >
<l i ><a href= " # " >Strony z Pol s ki <ja><jl i >
<l i ><a href= " # " >Strony z Twoj ego regi onu<ja><jl i >
<jul >
<h3>Czas<jh3>
<ul >
<l i ><a href= " # " >Naj nowsze<ja><jl i >
<l i ><a href= " # " >Ostatni e 2 dni <ja><jl i >
<l i ><a href= " # " >Wi ęcej narzędzi wysz u k i wani a<ja><jl i >
<jul >
<jnav>

<sect i on>
<header>
<h1>Wyn i ki dl a &quo t ; test&quo t ; <jh1>
<p>Okoto 1 410 000 000 wyn i ków ( 0 , 2 1 sekundy)<jp>
<jheader>

<art i c l e>
<header>
<h 1><a href= " #">Pi erwszy wyn i k<ja><jh1>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que senectus et netus et mal esuada fames ac turp i s egestas . <jp>
<footer>
<p>www. udawaj wi tryne . com - <a href= " # " >z pami ęci podręcznej <ja><jp>
<ul >
<l i ><a href= " # " >W i adomośc i <ja><jl i >
<l i ><a href= " # " >Za kupy<ja><jl i >
<l i ><a href= " # " >Zdj ę c i a<ja><jl i >
<l i ><a href= " # " >Sport<ja><jl i >
<l i ><a href= " #" > B i znes<ja><jl i >
48 Rozdział 1. Nowe e lementy stru kturalne w HTML5

<l i ><a href= " # " >Rozrywka<ja><jl i >


<l i ><a href= " # " >W i ęeej wyn i ków z udawaj w i t ryne . pl <ja><jl i >
<jul >
<jfooter>
<jart i el e>

<art i e l e>
<header>
<h l><a href= " # " >Drugi wyni k<ja><jhl>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<footer>
<p>www. udawaj w i t ryne2 . p l - <a href= " # " >z parni ęe i podręeznej <ja><jp>
<jfooter>
<jart i el e>
<art i e l e>
<header>
<h l><a href= " # " >Trzee i wyn i k<ja><j h l>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<footer>
<p>www. udawaj w i t ryne3 . p l - <a href= " # " >z parni ęe i podręeznej <ja><jp>
<jfooter>
<jart i e l e> < 1-- i tak dale]. .. _o>

<as i de>
<nav>
<h2>Wys z u k i wan i a powi ązane z &quot ; Test&quot ; <jh2>
<ul >
<l i ><a href= " #" > I nna <strong>w i t ryna<jstrong><ja><jl i >
<l i ><a href= " #" > I nna <strong>w i t ryna<jstrong><ja><jl i >
<l i ><a href= " #" > I nna <strong>w i t ryna<jstrong><ja><jl i >
<l i ><a href= " #" > I nna <strong>w i t ryna<jstrong><ja><jl i >
< 1-- etc. _o>
<jul >
<jnav>
<jasi de>

<jsee t i on>

<nav>
<ul >
<l i e l as s = " eurrentpage" > l<jl i >
<l i ><a href= " # " >2<ja><jl i >
<l i ><a href= " # " >3<ja><jl i >
<l i ><a href= " # " >4<ja><jl i >
<l i ><a href= " # " >5<ja><jl i >
<l i ><a href= " # ">6<ja><jl i >
< 1-- i tak dale]. . . _o>
<jul >
<jnav>

<form>
<fi el dset>
<l egend>Szukaj<jl egend>
<l abe l f or= " seareh i nput2 ">Szu kaj <jl abe l >
<i nput type=" seare h " i d = " seare h i nput 2 " name= " seare h i nput 2 " j>
<i nput type=" submi t " j>
<a href= " # " >Szukan i e zaawansowane<ja>
Tworzenie strony z wyn i kam i wyszu kiwania 49

<jf i e l dset>
<jform>

<footer>
<ul >
<l i ><a href= " #" > Regul ami n<ja><jl i >
<l i ><a href= " # " >Pol i tyka prywatnoś c i <ja><jl i >
< 1-- etc. -- >
<jul >
<jfooter>
<jbody>
<jhtml >

Szukanie nazwy przedsiębiorstwa

Szukaj (przał;upytan!lj Szukanie zaawansowane

Wy n i ki dla "test"
Dokładne OkOl<> l 4 1 0 000 000 wynik6w (0.21 sekundy)
szukanie
Wszystko WiadomoSa
� P i erwszy wyn i k
Sieć Pellenlesque habitani morbi tristique senedus et nelus e t malesuada fames ac turpis egestas.
\V\Wł udawajwitJyne ,com - zpamieąpodrecznej
Stronyz Polski Stronyz Wl8dOmOŚCI Zakupy Zd"ącl8 §QQ!! BIznes
Twojeoo regionu RozMvka Wiece!'łNmkÓW z udawajwlłrvne.ol
Czas
Nalnowsze Ostatnie 2 dni
Wlacej narzędzi D rugi wynik
wyszukiwama
Pellentesque haMant morbi trisłlQue senecłus et nelus et malęsui;Jda fames ec turpis egeslas
www.udawajwiuyne2. pl - zpamieclpodręcznei

Trzeci wyn i k
Pellentesque habltant morbi triSłlque senedus e t nelus et malesuada fames ae turpis egestas
www udaW8jWrlryne3. pl - zpamięcipodreClnel

Wyszukiwania powiązane z "Test"


Inna wibyna Inna witryna Inna witryna Inna witryna

Szukaj IPrześ!;zaprtani8! Szukanie zaawansowane


Requlamlll
PohM<a QNwalnoŚCI

Rysunek 1 . 14. Strona z wynikami wyszukiwania

Być może masz inne pomysły dotyczące tego, jakich znaczników użyć. To świetnie. Powinieneś myśleć
o tworzeniu pięknego kodu HTML.
Wszystkie rezultaty znajdują się we fragmencie (oznaczonym elementem sect i on) strony, który ma
nagłówek <h 1>Wyn i ki d l a &quot; test&quot ; </h 1>. Każdy kolejny rezultat j est umieszczony w osobnym
artykule, a wszystkie one maj ą nagłówek i stopkę. Rezultaty wyszukiwania mogłyby być rozdzielone na
kolejne podpunkty lub artykuły, w zależności od treści.
W dolnej części kodu znajdują się znaczniki umożliwiające przechodzenie na inne strony, co można uznać
za "główną nawigację" (przypomnij sobie, co mówi na ten temat specyfIkacja HTMLS), gdyż funkcjonalność
stronicowania ma kluczowe znaczenie dla sposobu, w jaki użytkownik nawiguj e po rezultatach swoich
wyszukiwań.
50 Rozdział 1. Nowe e lementy stru kturalne w HTML5

P od su mowanie
w tym rozdziale dowiedziałeś się o nowych elementach dostępnych w HTMLS, których możesz używać,
tworząc główną strukturę strony internetowej . Rozpocząłeś od nowego doctype i zmian związanych
z dołączaniem plików J avaScript i CSS. Następnie wykorzystałeś elementy h eader, hgroup, nav, footer,
art ; c l e, sect ; on i as ; de do utworzenia układu strony. Wreszcie, dzięki weryfikacji konspektu dokumentu,
sprawdziłeś, czy struktura Twojego dokumentu j est poprawna i czy użyłeś właściwych elementów.
2
Grupowanie, poziom tekstu
i zmiany semantyczne

W
poprzednim rozdziale poznałeś kilka nowych elementów HTMLS, które umożliwiają tworzenie
głównej struktury strony. W tym rozdziale przeczytasz o kolejnych nowych elementach (mianowicie
fi gure, t i me, deta i l s i mark) oraz o kilku elementach, które zostały przedefiniowane (address, s, c i te, o l ,
d l , smal l , b, strong, i , em, abbr oraz hr). Przyjrzymy się również nowym łączom blokowym oraz specyfikacji
WAl-ARIA. Elementy te są nazywane elementami grupującymi albo elementami poziomu tekstu i dotyczą
treści strony.

PRZEPIS DLA POCZĄTKUJĄCYCH


Oznaczanie rysunków i ich podpisów
elementami figure i figcaption
Element fi gure pozwala opakować obrazek i nadać mu opis. Wcześniej trzeba było użyć elementu d i v
lub czegoś podobnego, a następnie dodać do strony tekst - co oznaczało, że nie było żadnego powiązania
między obrazkiem a jego podpisem. Teraz jednak, wykorzystuj ąc element fi gure, możesz powiązać obrazki
z podpisem przy użyciu elementu fi gcapt i on.
Ponadto fi gure nie musi zawierać obrazka; może zawierać fragmenty kodu, dane tabelaryczne, dźwięk
lub wideo. Zwykle jednak fi gure będzie używany do oznaczania obrazków (rysunek 2 . 1 ) . Kod użyty do
utworzenia rysunku 2 . 1 jest pokazany na listingu 2 . 1 .

Listing 2 . 1 . Obrazek z podpisem

<fi gure>
< i mg a l t = " Wykres s tup kowy" src = " analyt i c s . g i f" j>
<fi gcapt i on>
Anal i za danych dotyczących wi tryny za paźdz i erni k 2010
<jf i gcapti on>
<jfi gure>
52 Rozdział 2. Gru powanie, poziom tekstu i zm iany semantyczne

Liczba odwiedzin rośnie, spada i znowu rośnie

I listopada 20 I O

Pellentesque habitant morbi tristique. senectus et netus et malesuada fames ac turpis egestas . Vestibulum
tortor quam, feugiat vitae, ultricies eget, te,mpor sit amet� ante. Donec fU libero sit amet quam egestas semper.
Aenean ultricies mi \"itae es!. Mauris placerat eleifend leo. Ut felis. Praesent dapibus, neque id cursus
faucibus, tortO! neque egestas augue� eu vulputate magna eros eu erat. Aliquam erat volurpat. Nam dui mi�
tincidunt quis, accumsan porttitor, facilisis luctus: metus

Pellentesque habitant morbi


tristique senectus et netus et
ma1esuada fames ac turpis
egestas. Vestibulum tottor quam,
feugiat vitae, ultricies eget,
tempor sit amet, ante. Donec eu
libero sit amet quam egestas

11
semper. Aenean ultricies mi
vitae est. Mauris placerat
eleifend le,o. Quisque sit amet

II
est et sapien ullamcorper

•••
pharetra Vestibulum erat wisi,
c.ondimentum sed� c.ommodo
vitae, ornare sit amet, \\risi.
Aenean fermentum, elit eget
tincidunt condimentum, eros
ipsum rutrom orc.i� sagirtis Analiza danych dotyczqcych witryny za październik 20 l O
tempus lacus enim ac. dUl. Donec
non enim in turpis pulvinar
facilisis . Ut felis. Praesent dapibus, neque id cursus fauc.ibus, tortor neque ege.stas augue, eu 'V-ulputate magna
eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, fac.ilisis luctus, metus

Rysunek 2 . 1 . Element figure użyty do wyświetlenia wykresu wraz z podpisem

są pewne niejasności dotyczące tego, czy tekst umieszczany w atrybucie a l t (tekst alternatywny dla
przeglądarek, które nie obsługują obrazków) jest nadal potrzebny w elemencie fi g ure. Umieszczony poza
elementem fi g ure znacznik i mg zawsze wymaga atrybutu al t. Jeśli obrazek jest czysto prezentacyjny i nie
musi być identyfIkowany przez technologie ułatwień dostępu, można zastosować pusty atrybut a l t. W sytuacji
gdy użyto elementu figure, jeśli podpis może być jednocześnie odpowiednim opisem, a l t nie jest potrzebny.
Obecnie jednak, w związku z niedostateczną obsługą technologii ułatwień dostępu w przeglądarkach, takie
rozwiązanie nie jest optymalne.
Sugeruj emy, aby wykazać się raczej nadmierną ostrożnością i mimo wszystko nadawać wartość
atrybutowi a l t. Na listingu 2.1 podpis jest wystarczaj ąco jasny, lecz dla kogoś używającego czytnika ekranu
nie jest wiadome, jak dane są reprezentowane, a wtedy tekst w atrybucie a l t dostarczyłby tej informacji.
Zwróćmy dodatkowo uwagę, że pomimo iż użyliśmy w przykładzie obrazu z wykresem, nie ma powodu,
dla którego nie mógłbyś użyć wykresu utworzonego za pomocą Canvas albo SVG.

I
Uwaga
Początkowo specyfikacja zalecała użycie Uuż istniejącego) elementu l egend zam iast nowego elementu
(fi gcapt i on), lecz ze względu na problemy z n ad awan iem stylów w przeglądarkach l egend zarzucono
na rzecz fi gcapt i on .

J ak pokazuje rysunek 2.2, nie trzeba się ograniczać tylko d o jednego obrazka w elemencie fi gure;
możesz używać elementu f i gure do wyświetlania wielu obrazków. Kod dotyczący rysunku 2.2 znajduje
się na listingu 2.2.
Oznaczanie d aty i czasu za pomocą elementu time 53

Listing 2.2. Wiele obrazków w jednym elemencie figure

<fi gure>
< i mg a l t = " Wykres s tup kowy danymi paźdz i ern i ka 2 0 1 0 " src = " anal yt i cs-october . png" j>
< i mg a l t = " Wykres s tup kowy z danymi z l i stopada 2 0 1 0 " src= " anal yt i cs-november. png" j>
< i mg a l t = " Wykres s tup kowy z danymi z grudni a 2 0 1 0 " src = " analyt i c s -december . png" j>
<fi gcapt i on>
Anal i za porównawcza danych wi tryny za j es i eń 2010
(paźdz i ern i k, l i stopad, grudz i eń)
<jf i gcapti on>
<jfi gure>

Liczba odwiedzin j esienią 2010

l stycznia 201 1

Pellentesque habitant mochi tristique. senectus et netus et malesuada farnes ac turpis egestas. Vestibulum
tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas
semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

.1.1
Analiza porównawcza danych witryny zajesień 20 l O (październik, listopad, grudzie/z)

Pe IJentesque habitant mocbi tristique senectus et netus et malesuada farnes ac turpis egestas. Vestibulum
tonor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas
semper. Aenean ultricies mi vitae es" Mauris placerat eleifend leo. Quisque sit amet es! et sapien
ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi.

Rysunek 2 . 2 . Użycie elementu figure do wyświetlenia trzech obrazków współdzielących jeden podpis

Czy trzeba zawsze używać fi gcapt i on, żeby wyświetlić taką treść? Jeśli obrazek (albo wykres, tabela itp.)
służy celom czysto prezentacyjnym, użyj zwykłego tagu i mg. Jeśli jednak zawiera on dodatkowe informacje
i jest istotny dla treści strony, prawdopodobnie będzie wymagał opisu - w takim przypadku użyj elementów
fi gure i fi gcapt i on. Na koniec dodajmy, że fi gure może mieć tylko jeden fi gcapt i on.

PRZEPIS DLA POCZĄTKUJĄCYCH


Oznaczanie daty i czasu za pomocą elementu time
Element t ime pozwala zakodować datę i czas w sposób możliwy do odczytania przez komputery, a jednocześnie
jest wyświetlany przez przeglądarki w postaci czytelnej dla użytkowników. Możesz więc za j ego pomocą
oznaczyć na przykład daty publikacji i zdarzeń, które mogą być wykorzystane z poziomu innych technologii
(oczywistym przykładem j est kalendarz) . Element t i me ma dwa opcjonalne atrybuty:
54 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne

• datet i me - użytkownik końcowy zobaczy treść znajdującą się wewnątrz znacznika t i me, lecz
komputer będzie w stanie odczytać wartość atrybutu datet i me: datet ime= "2011 -04-01 T16 : OOZ".
Część dotycząca czasu (T1 6 : 00) jest opcjonalna. Możesz również dodać informację o poprawce
związanej ze strefą czasową: T16: 00+04 : 00. Znak Z reprezentuje uniwersalny czas koordynowany
(UTC), a j ego użycie jest równoznaczne z dodaniem poprawki o wartości +00 : 00 .
• pubdate - pubdate jest atrybutem logicznym. Wskazuje, że data oraz (być może) czas dotyczą
publikacji najbliższego rodzicielskiego elementu art i c l e. Jeśli nie ma rodzicielskiego elementu
art i ci e, wtedy pubdate odnosi się do całego dokumentu. Każdy element art i ci e powinien mieć
tylko jeden element t ime z atrybutem pubdate.

Element t i me jest przeznaczony do pokazywania dokładnych dat, takich j ak ,,22 stycznia 2 0 1 1 ", a nie
nieprecyzyjnych, takich jak "w jakimś momencie roku 20 1 1 " . Wartość atrybutu dat et i me musi być
podana w formacie stosowanym w kalendarzu gregoriańskim: RRRR-MM-DD, z czasem zakodowanym
w postaci TOO : 00.
Oto kilka przykładów:
<art i c l e> . . . .
<footer>
<p>Ta wi adomość zostata opubl i kowana dni a <time pubdate dateti me="2011-04-01T16 :00">1 kwi et n i a 2 0 1 1
-" 0 godz i n i e 1 6 : 00</time> przez <a href= " # " >Jana Kowa l s k i ego<ja><jp>
<jfooter>
<jart i c l e>
<art i c l e>
<h 1>Rodz i nne zdj ęc i e bożonarodzeni owe<jh1>
<p>Byto wspan i al e być tutaj z rodz i ną w czas i e <time pubdate datetime= "2010-12- 25">Bożego Narodze n i a
-"2 0 1 0</time> <jp>
<fi gure>
< i mg al t = " " src = " OOOOO l . j p g " j>
<fi gcap t i on>Rodz i na Kowa l s k i c h w czas i e bożonarodzeni owego poranka<jfi gcapt i on>
<jfi gure>
<jart i c l e>
<p>Publ i kacja HTML6 j est ocze k i wana w dn i u <time datetime="2040-01-04">1 kwi etn i a 2 0 4 0</time> <jp>

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie widżetu przełącznika
za pomocą elementu details
W czasie pisania tej książki tylko przeglądarka Chrome 1 3 + obsługiwała nowy element deta i l SI. Mamy
nadzieję, że inne przeglądarki będą go obsługiwać raczej wcześniej niż później .
Element deta i l s tworzy efekt interaktywnego przełącznika typu "otwórz/zamknij" bez potrzeby
używania J avaScriptu i/lub CSS-a. W ramach elementu deta i l s do wyświetlania podsumowania treści
można użyć elementu s ummary.
Element deta i l s ma opcjonalny atrybut open, który - j eśli j est ustawiony - powoduje domyślne
pokazywanie otwartego elementu deta i l s; jeśli nie, element deta i l s będzie zamknięty i wyświetlona będzie

1 Zgodnie z informacjami podanymi w witrynie http://caniuse.com/, o której wspomniano w rozdziale 3., w kwietniu 2012 roku
obsługiwała go także przeglądarka systemu Android -przyp. tłum.
Tworzenie widżetu przełączn i ka za pomocą elementu details 55

zawartość elementu s unrnary. Użytkownik może kliknąć treść z elementu s ummary, co spowoduje otwarcie
bądź zamknięcie szczegółów.
Rysunek 2.3 pokazuje, j ak wygląda krótka informacj a o autorach z górną częścią domyślnie otwartą.
Listing 2.3 prezentuje kod.

Listing 2 . 3 . Przykład użycia elementu details

<deta i l s ap en>
<summary>Tam Leadbetter<jsummary>
<fi gure>
< i mg al t= " " sre = " i magesjtam-and - l uee . j pg" j>
<fi geapti an>Tam i Luey Leadbetterawi e<jfi geapti an>
<jfi gure>
<p>Pel l entesque habi tant marbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<p>Pel l entesque habi tant marbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<p>Pel l entesque habi tant marbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<p>Pel l entesque habi tant marbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<jdet a i l s>
<deta i l s>
<summary>Chuek Hudsan<jsummary>
<fi gure>
< i mg al t=" " src=" c huc k . j pg" />
<fi geapti an>Chuek Hudsan<jfi geapt i an>
<jfi gure>
<p>Pel l entesque habi tant marbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
<jdet a i l s>

.... Tom Leadbetter

Pellentesque habitant morbi


tristique senectus et netus et
malesuada fames ae turpis
egestas.

Pellentesque habitant morbi


tristique senectus et netus et
malesuada fames ae turpis
egestas.

Pellentesque habitant morbi


tristique senectus et netus et
malesuada fames ae turpis
egestas_
Tom i Luey LeadbetterO\vie

Pellentesque habitant morbi


tristique senectus et netus et mruesuada fames ac turpis egestas_

� Chuek Hudson

Rysunek 2.3. Element details zjedną sekcją otwartą w przeglądarce Chrome

Innym przykładem może być wykorzystanie elementu deta; l s do pokazywania lub ukrywania spisu
treści. W zależności od stylów wykorzystanych na stronie i ilości treści użyteczne może być umieszczenie
spisu treści tak, by był zawsze widoczny w górnym rogu strony. Użytkownik mógłby go kliknąć w celu
rozwinięcia go i przejścia do innej części strony. Listing 2.4 pokazuj e kod takiego rozwiązania, z elementem
deta; l s domyślnie zamkniętym.
56 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne

Listing 2.4. Tworzenie zwijalnego spisu treści

<art i c l e>
<h l>Ol brzymi dokument z mnóstwem soczystej treśc i </ h l>
<deta i l s>
<summary>Sp i s treś c i </summary>
<nav>
<ul >
<l i ><a href=#chapterl>Rozdz i at l . </a></l i >
<l i ><a href=#chapter2>Rozd z i a t 2 . </a></l i >
<ful >
</nav>
</deta i l s>
<sect i on>
<hl i d= " chapter l " > Rozdz i at l . </ h l>
</sect i on>
<sect i on>
<hl i d= " chapter2 ">Rozdz i at 2 . </ h l>
</sect i on>
<fart i c l e>

PRZEPIS DLA POCZĄTKUJĄCYCH


Użycie elementu address do danych kontaktowych
Specyfikacja definiuje element address jako element tworzący oddzielną sekcję, taki jak nav albo art ; e l e.
Jednak umieściliśmy go w tym rozdziale, ponieważ uważamy, że jest on bardziej związany semantycznie
z poziomem tekstu, gdyż jego użycie odnosi się raczej do treści tekstowej niż układu.
Przez wiele lat element address był używany nieprawidłowo przez twórców stron internetowych
(często na stronie "Kontakt") . Nie powinien on być wykorzystywany jako zwykły adres pocztowy.
A zatem poniższy kod jest nieprawidłowy:
<address>
Jak Kowal s k i
ul . Moj a l
Pol s ka
</address>

HTMLS próbuje wyj aśnić nieporozumienie poprzez zalecanie, by element address był używany
do przechowywania danych kontaktowych dotyczących najbliższego elementu art; e l e albo body.
Co to w takim razie oznacza? Oznacza to, że powinieneś używać elementu address do danych
kontaktowych autora bieżącego artykułu (a rt ; e l e) albo całej strony. Ponieważ możesz użyć elementu
address wewnątrz elementu art ; el e, addres s potencjalnie może być wykorzystany w witrynie kilka razy.
Treścią elementu address może być adres e-mail, adres strony internetowej, numer telefonu, adres pocztowy
albo j akiś inny rodzaj danych kontaktowych.
Ponieważ jest on używany do danych kontaktowych, zwykle umieszcza się element addres s wewnątrz
elementu footer. Na listingu 2.5 użyliśmy elementu address dwukrotnie: raz do umieszczenia informacji
o autorze treści strony głównej, a drugi raz do podania kontaktu do autorów całej witryny.
Podświetlanie tekstu za pomocą e lementu mark 57

Listing 2 . 5 . Wielokrotne użycie elementu address

<art i e l e>
<header>
<h l><a href= " # " >Mój n i ezwykl'y wp i s na bl ogu<ja><jhl>
<p> 12 . 12 . 20 1 1<jp>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . . . <jp>
<footer>
Ten wpi s zostal' nap i s any przez
<address><a href="me . htm">Jana Kowal ski ego</a></address>
<jfooter>
<jart i e l e>
<footer>
Wl'aśe i e i el ami tej w i t ryny są
<address>
<a href= "me . htm">Jan Kowal ski</a> i
<a href=mai l to : pi otr@examp l e . com>Pi otr Nowak</a> .
</address>
<jfooter>

PRZEPIS DLA POCZĄTKUJĄCYCH


Podświetlanie tekstu za pomocą elementu mark
Element mark daje autorowi dokumentu możliwość podkreślenia jakiegoś fragmentu albo przyciągnięcia
do niego uwagi.
Jeśli użytkownik przeszukuje witrynę i jest przenoszony na oddzielną stronę, pojęcie, którego szukał,
może być podświetlone. Użylibyśmy tutaj raczej elementu mark niż strong albo em, ponieważ nie nadajemy
temu pojęciu żadnego znaczenia ani nie kładziemy na niego nacisku, ale po prostu wyróżniamy je dla
użytkownika. Rysunek 2.4 pokazuje efekt użycia tego elementu, a listing 2.6 prezentuje jego kod.

Listing 2.6. Element mark z dodatkowym CSS-em

<styl e>
mark {background- co l o r : #OFO; font-wei ght :bol d ; }
<jstyl e>
<art i e l e>
<header>
<h l>Coś nap i s anego po l'ae i n i e<jh l>
<jheader>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas .
�<mark>Vest i bul um tortor quam</mark> , feu g i at v i tae, ul tr i e i es ege t , tempor s i t amet , ante . Donee eu
�l i bero s i t amet quam egestas semper. Aenean ul tri e i es mi vi tae e s t . Mauri s pl aeerat el e i fend l eo . <jp>
<jart i e l e>

Coś napisanego po łacinie

Pellentesque habitant morbi tristique seneetus et netus et malesuada fames ae turpis egestas. iStIb
........
. ..".
., .., feugiat vitae, ultricies eget, tempor sit amet, ante. Donee eu libero sit amet quam egestas
semper. Aenean ultricies mi vitae e st. Mauris placerat eleifend leo.

Rysunek 2.4. Element mark użyty do wyróżnienia tekstu


58 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne

PRZEPIS DLA POCZĄTKUJĄCYCH


Użycie elementu s do pokazania nie poprawnej
lub nieistotnej treści
Wcześniej element s był przeznaczony j edynie do przekreślania tekstu. W HTMLS został przedefiniowany
i j est teraz używany do prezentowania treści, która nie jest już poprawna albo istotna.
Co to dokładnie znaczy? Mógłbyś użyć elementu s do prezentowania oryginalnej detalicznej ceny
produktu, który teraz ma inną cenę, jak pokazano na rysunku 2.5. W zależności od kontekstu wyświetlanie
przestarzałej informacji nie zawsze jest pożądane. Jednak w tym przypadku może się ona okazać przydatna
dla użytkownika. Listing 2.7 pokazuje sposób użycia elementu.

Listing 2 . 7 . Element s

<hl>Autob i ografi a Jana Kowa l s ki ego<jhl>


<p><s>Za l ecana cena detal i czna : 45 , 99 zf</s><jp>
<p><strong>Teraz dostępna za j edyne 5 , 99 zf ! <jstrong><jp>

Autobiografia Jana Kowalskiego

Zaleeanaeeaaeietaliezaa: 45,99zł

Teraz dostępna za jedyne 5,99 złl


Rysunek 2 . 5 . Element s użyty do wyświetlenia starej ceny

W HTML 4 element s definiował przekreślony tekst, więc domyślnie przeglądarki będą nadawać temu
elementowi styl przekreślenia.
Jeśli fragment dokumentu został przeredagowany lub usunięty, nie używaj elementu s; zamiast tego
skorzystaj z elementu del .

Zmiany dotyczące istniejących elementów


W pierwszym rozdziale poznałeś nowe elementy HTMLS, które mogą być użyte do tworzenia układu strony
i dodawania treści. W tym rozdziale przedstawiliśmy jeszcze kilka nowych elementów, ale dotyczyły one samej
treści - tak jak w przypadku obrazków. To, że skupiliśmy się tak bardzo na nowych elementach, nie oznacza,
że wcześniej istniejące elementy zostały zaniedbane. W rzeczywistości wiele z nich zmieniło swoj ą rolę.

Element cite
Element c ; te w HTMLS został poprawiony. W HTML 4 c ; te pozwalał twórcom treści oznaczać nazwisko
autora cytatu:
<ci te>Jul i usz Cezar<j c i te> pow i edzi af ki edyś <q>przybyfem , zobaczyfem, zwyc i ężyfem<jq> .

Był on również używany wewnątrz elementu b l ockquote, co w zasadzie w HTML 4 było niepoprawne,
niemniej często stosowane:
Zm iany dotyczące istniej ących elementów 59

<bl oc kquote>
<p>Pel l entesque hab i t ant morbi t r i s t i que senectus et netus et mal esuada fames ac turp i s egestas . Ves t i bul um
�tortor quam, feu g i at v i tae, ul tr i c i es ege t , tempor s i t amet , ante . <jp>
<ci te>Osoba , która mów i t a po tac i n i e<jci te>
<jb l oc kquote>

Natomiast w HTMLS c; te reprezentuje tytuł dzieła, jak książka albo piosenka. Specyfikacja HTMLS
określa precyzyjnie, że nazwisko osoby nie j est tytułem dzieła. Mógłbyś zatem użyć czegoś takiego j ak
w następującym przykładzie:
<p>Jedną z mo i ch ul ubi onych ks i ążek j est <c i te>Dz i eń Szakal a<j c i te> <b>Frederi c ka Forsytha<jb><jp>

(Specyfikacja HTMLS sugeruj e wykorzystanie elementu b do oznaczania nazwiska autora) .


Ta zmiana w HTMLS, zabraniająca umieszczania nazwisk autorów w elemencie c ; te, spowodowała
trochę zamieszania. Warto przeczytać artykuł http://24ways.org/2009/incite-a-riot Jeremy'ego Keitha, który
dogłębnie analizuje to zagadnienie. Podsumowując, element c ; te w HTMLS nie jest już kompatybilny wstecz,
a zamiast niego specyfikacja HTMLS sugeruj e używanie do oznaczania nazwisk elementu b, chociaż ten
znacznik nie ma żadnego znaczenia (semantyki), nawet jeśli jego treść ma znaczenie.
Musisz więc podjąć decyzję: czy robić tak, jak zaleca specyfikacja, czy - tak jak wielu robi to nadal
- używać c ; te do oznaczania nazwisk. Warto obserwować element c ; te i sprawdzać, czy jego definicja
się nie zmienia.

I
Uwaga
W l i pcu 2011 roku odbyła się dyskusja, której skutkiem może być umożliwienie użycia e lementu
footer w elemencie b l ockq uote, co stworzyłoby możl iwość dodawan ia inform acj i o cytacie,
n a przykład dotyczącej autora. Miej o ko na tę s prawę.

Element ol
Element o l , używany do tworzenia listy uporządkowanej, został przedefiniowany i teraz akceptuje trzy
atrybuty:
• start,
• reversed,
• type.

Użyty na listingu 2.8 atrybut reversed jest nowością w HTMLS i - w założeniu - umożliwia utworzenie
odwróconej listy, odliczającej w dół do jednego.

Listing 2.8. Odwrotnie uporządkowana lista

<hl>Moje ul ubi one kol ory<jhl>


<ol reversed>
<l i >czerwony<jl i >
<l i >z i e l ony<jl i >
<l i >ni ebi es k i <jl i >
<jo l >

Google Chrome pokazuje t o w następujący sposób:


60 Rozdział 2. Gru powanie, poziom tekstu i zm iany semantyczne

Moje ulubione kolory:


3. czerwony
2. zielony
1 . niebieski

Atrybut start w HTML 4 był uznawany za przestarzały i w związku z tym, gdy został użyty na jakiejś
stronie, to strona ta nie przechodziła walidacji, co było dość irytujące. Szczęśliwie atrybut ten powrócił
i w HTMLS jest zupełnie prawidłowy. Jeśli więc musisz rozpocząć uporządkowaną listę od drugiej pozycji,
użyj następującego kodu:
<ol start = " 2 " > <l i >tutaj
zaczynamy</l i >

</0 1 >

Również atrybut type powrócił z zaświatów. Wcześniej, jeśli chciałeś zmienić sposób wyświetlania listy,
aby pokazywać kolejne punkty, powiedzmy, jako liczby rzymskie (na przykład I, IV, X), musiałeś użyć CSS­
a. W HTMLS możesz to zrobić jak na listingu 2.9.

Listing 2 . 9 . Zagnieżdżone uporządkowane listy

<ol >
<l i > Lorem i psum dol ar s i t amet , consectetuer ad i p i s c i ng el i t .
<ol>
<l i >Lorem i psum dol ar s i t ame t , consectetuer adi p i s c i ng el i t . </l i >
<l i >Al i quam t i nc i dunt maur i s e u r i s us . </l i >
<l i >Lorem i psum dol ar s i t ame t , consectetuer ad i p i s c i ng el i t .
<ol >
<l i >Al i quam t i nc i dunt mauri s eu r i sus . </l i >
<l i >Vest i bul um auctor dap i bus nequ e . </l i >
</0 1 >
c/l i >
<l i >Ves t i bul um auctor dapi bus neque . </l i >
</01 >
c/l i >
<l i >Al i quam t i nc i dunt mauri s e u r i s us . </l i >
<l i >Vest i bul um auctor dap i bus neque . </l i >
</0 1 >

Pokazany kod wyświetli następujący rezultat:


1 . Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
1 . Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
2. Aliquam tincidunt mauris eu risus.
3. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
1 . Aliquam tincidunt mauris eu risus.
2. Vestibulum auctor dapibus neque.
4. Vestibulum auctor dapibus neque.
2. Aliquam tincidunt mauris eu risus.
3. Vestibulum auctor dapibus neque.
Zm iany dotyczące istniej ących elementów 61

Używając atrybutu type, możesz zmienić rodzaj numeracji elementów listy bez potrzeby używania CSS-a.
Możesz wybierać spośród pięciu rodzajów:
type= " 1 " - 1, 2, 3, 4, 5
type= " a " - a, b, c, d, e
type = " A " - A, B, C, D, E
type= " i " - i, ii, iii, iv, v
type= " I " - I, II, III, IV, V

Jeśli zmienisz typ elementu o l na jeden z powyższych, elementy listy zostaną pokazane następująco :
1 . Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
a. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
b. Aliquam tincidunt mauris eu risus.
c. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
i. Aliquam tincidunt mauris eu risus.
ii. Vestibulum auctor dapibus neque.
d. Vestibulum auctor dapibus neque.
2. Aliquam tincidunt mauris eu risus.
3. Vestibulum auctor dapibus neque.

Dzięki użyciu różnych wartości atrybutu type w treści możesz się odwolywać do pozycji 1 .b.ii zamiast 1 .3.2.

Element dl
W HTML 4 element d l oznaczał listę definicji, która powinna zawierać pojęcie, a następnie definicję - lecz
jego własna definicja i sposób użycia nigdy nie były jasne i w związku z tym był on używany niewłaściwie lub
zastępowany innymi elementami.
W HTMLS określono, że ma on być listą opisów lub listą asocjacyjną. Łatwiej jest zrozumieć ten element
na przykładach. Na listingu 2 . 1 0 użyliśmy elementu dl do utworzenia słownika. Umieściliśmy ten słownik
w elemencie as i de, ponieważ zakładamy, że znajduje się on wewnątrz elementu art i cl e z zawartością na
temat tworzenia stron internetowych.

Listing 2 . 10 . Tworzenie słownika

<as i de>
<h2>Stowni k<jh2>
<dl >
<dt>HTML<jdt>
<dd>HTML, będący akronimem pochodzącym od ang i e l s ki ego HyperText Markup Language, j est domi nuj ącym j ęzyki em
�znaczn i ków stużącym do tworzen i a stron i nternetowyc h . <jdd>
<dt>PHP<jdt>
<dd>PHP (Hypertext Preprocessor) j est szeroko używanym j ęzyk i em s kryptowym ogól nego przeznaczen i a , który byt
�zaproj ektowany do tworzen i a dynam i c znych stron i nternetowyc h . <jdd>
<jd l >
<jasi de>
62 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne

Na listingu 2 . 1 1 użyliśmy elementu dl do zapisania listy osób biorących udział w produkcji filmu.

Listing 2 . 1 1 . Osoby biorące udział w produkcji filmu

<hl>S kazani na Shawshank<jhl>


<dl >
<dt>Reżyser<jdt>
<dd>Frank Darabont<jdd>
<dt>Scenari usz<jdt>
<dd>Stephen Ki ng<jdd>
<dd>Frank Darabont<jdd>
<dt>Obsada<jdt>
<dd>T i m Robbi ns<jdd>
<dd>Morgan Freeman<jdd>
<dd>Bob Gunton<jdd>

<jd l >

W powyższym kodzie użyliśmy wielu wartości (dd) przypisanych d o j ednego klucza (dt). Można
argumentować, że każda pozycja na liście osób mających udział w wyprodukowaniu filmu (reżyser, autorzy
scenariusza i tak dalej) mogłaby się znajdować we własnym elemencie sect i on, jak pokazano tutaj :
<art i c l e>
<header>
<hl>S kazani na Shawshank<jhl>
< t i me>1994<jt i me>
<jheader>
<sect i on>
<hl>Reżyser<jh l>
<h2>Fran k Darabont<jh2>
<p> (bi o) <jp>
<jsec t i on>
<sect i on>
<hl>Scenari usz<j h l>
<h2>Stephen Ki ng<jp>
<p> (bi o) <jp>
<h2>Fran k Darabont<jh2>
<p> (bi o) <jp>
<jsec t i on>
<jart i c l e>

Tak naprawdę zależy to od treści strony i od tego, jak chcesz tę treść ustrukturyzować.

Wskazówka
Element dl był dawn iej używany do oznaczania d i alogów, lecz teraz s pecyfi kacj a mówi n a m , że takie
wykorzystanie elementu dl jest niewłaściwe. Początkowo w HTML5 istn iał e lement d i a l og, lecz został
on usun ięty pod kon iec 2009 roku . Zam iast n iego powin ieneś używać e lementów p, a jeś l i chciałbyś
nadać styl im ionom rozmawiaj ących, powinieneś zastosować albo e lement span, albo b. Oto przykład :

<p><b>J ohn<jb> : Czy można j uż używać HTML5?<jp>


<p><b>Jane<jb> : Tak, zdecydowa n i e można ! <jp>
Zm iany dotyczące istniej ących elementów 63

Element smali
W HTML 4 element sma 1 1 był używany do zmniejszania rozmiaru tekstu. Jednak w sprawach związanych
z prezentacją powinniśmy używać CSS-a. Teraz, w HTMLS, element sma 1 1 jest używany do wyświetlania
"drobnego druku", takiego j ak informacj a o prawach autorskich, regulamin albo informacj e licencyjne
bądź prawne:
<p><sma 1 1 > Ta w i t ryna j es t dostępna na l i cencj i <a href= ''http : //crea t i vecommons . org/
�l i censesjby-nc-saj3 . 0jpl j ">Creat i ve Commons Uznan i e autorstwa-Użyc i e n i ekomercyj ne-Na tych samych
�warunkach 3 . 0<ja> . Możesz ją zmi eni a ć , używać ponown i e i rozszerzać . <jsmal l ><jp>

Ponieważ sma 1 1 jest treścią wewnętrzną, możesz umieścić go w razie potrzeby w innym elemencie,
takim jak st rong, co nadałoby wagi tym podanym drobnym drukiem informacjom:
<p><strong><smal l >Ta treść nal eży do mni e ! N i e krad n i j j ej , bo w przec i wnym wypadku będz i esz mi at bardzo
�poważne ktopoty. <jsmal l ><jstrong><jp>

Elementy b oraz strong


w HTML 4 element b oznaczał pogrubiony tekst, ale teraz ma on cel czysto prezentacyjny; powinien być
używany do nadania stylu fragmentowi tekstu, który nie przekazuje żadnej szczególnie ważnej informacji.
Często na przykład pierwszy akapit wpisu na blogu ma nadany odmienny styl, nierzadko jest to pogrubiona
czcionka:
<h2>Ci emna energ i a i ptaski wszec hświ at odkryte prostą metodą<jh2>
<p><b cl ass= " l ead ">Naukowcy rozwi nęl i prostą techni kę, która uprawdopodabni a teor i ę , że wszec hświ at j est
�ptas ki . <jb><jp>
<p>Co wi ęcej , użyta metoda - rozw i n i ęta d z i ęki powrotowi do pomys tu sprzed 30 l at - potwi erdz a , że " c i emna
�energ i a" wypetni a prawi e trzy czwarte wszechśw i at a . <jp>

Nie powinieneś używać w takiej sytuacji elementu strong, ponieważ nie chcesz dodawać ważności
pierwszemu akapitowi; po prostu nadajesz mu inny styl. Zamiast używać elementu b, możesz skorzystać
z CSS-a (p : fi rst-of-type albo h2+p). Na listingu 2 . 1 2 zastosowaliśmy element b do wyróżnienia fragmentów
tekstu kolorem.

Listing 2 . 1 2 . Element b

<styl e>
b . red { c o l or: red ; }
b . green { co l or : green ; }
b . b l ue { col or: bl ue ; }
<jstyl e>
<hl>Moje ul ubi one kol ory<jhl>
<ol reversed>
<l i ><b cl ass="red">czerwony</b><jl i >
<l i ><b cl ass="green">z i el ony</b><j l i >
<l i ><b cl ass="bl ue">ni ebi es k i </b><jl i >
<jo l >
64 Rozdział 2. Gru powanie, poziom tekstu i zm iany semantyczne

Element strong pokazuje tekst mający duże znaczenie, więc teraz powinieneś używać go do uzyskania
efektu pogrubienia. Możesz zagnieżdżać elementy st rong, by zwiększyć powagę treści:
<p><strong>Ni e j edz moi ch ci astek</strong> ani <strong><strong>n i e p i j mojego ml e ka</strong></strong><jp>

Elementy i oraz em
Element ; służył w HTML 4 do wyróżniania tekstu kursywą. Teraz jednak oznacza się nim tekst, który jest
wyrażony innym głosem lub w jakiś sposób odbiegający od głównego tekstu. Specyfikacja HTMLS podaje
kilka przykładów jego użycia; korzystamy z niego, by zapisać jakąś myśl, pojęcie techniczne czy idiom
z języka obcego:
<p>Zjem dz i ś w i eczorem rybę < i > (a potem może z j em c i astec z k a ; od dawna n i e j adfem c i astec z e k ) <j i > . <jp>

Dla odmiany element em oznacza podkreślenie, które zmienia sens zdania. Słowa, które powinny być
zaakcentowane, opakuj elementem em, pamiętając, że zmiana położenia elementu em spowodowałaby zmianę
sensu zdania:
<p>Ja myś l afem , że mam s i ę spot kać z przyj a c i ófmi o 20 : 00 , a l e moja żona mówi , że o <em>2 1 : 00<jem><jp>
<p><em>Ja<jem> myśl afem, że mam s i ę spotkać z przyj aci ófmi o 20 : 00 , a l e moja <em>żona<jem> mówi , że o 2 1 : 00<jp>

Element abbr
Element abbr nie jest nowy w HTMLS i nie został przedefiniowany. Po c o więc zawracać sobie nim głowę?
Cóż, abbr został połączony z elementem acronym. Teraz element abbr reprezentuje wszystkie skróty. Atrybutu
t ; t l e możesz użyć do rozwinięcia skrótu, aby dać użytkownikowi podpowiedź:
<p><abbr t i tl e=" HyperText Markup Language">HTML<jabbr> j est standardem używanym przez twórców stron
�i nternetowych<jp>

W HTML 4 były dostępne oba znaczniki: abbr na oznaczenie typowych skrótów, j ak na przykład "dr",
i acronym dla akronimów, jak choćby "NATO". Ponieważ jednak często autorzy mieli wątpliwości co do tego,
którego z nich użyć, acronym został odrzucony i pozostał tylko abbr na oznaczenie ich obu.

Element h r
Element h r był używany do tworzenia poziomej kreski w dokumencie. Jego definicja została nieznacznie
poprawiona i teraz oznacza on przerwę po akapicie, taką jak zmiana miej sca akcji w książce. Zwykle jest
wyświetlany jako kreska albo wymyślny element graficzny pomiędzy fragmentami tekstu. Nie j est on obecnie
używany zbyt często, ponieważ w celu wstawienia dodatkowego miejsca, obrazka, kreski czy ozdoby po
odpowiednich fragmentach tekstu albo przed nimi, reprezentowanych przez elementy takie jak p, d ; v,
art ; c l e albo sect ; on, można użyć CSS-a.

Elementy, których już nie ma


w HTMLS usunięto kilka elementów; pożegnaj się z:
• acronym (używaj abbr; patrz wyżej),
• appl et (używaj obj eet),
• basefont (używaj CSS-a w celach związanych z prezentacją),
O pakowywanie elementów łączam i 65

• b i g (używaj CSS-a w celach związanych z prezentacją),


• center (używaj CSS-a w celach związanych z prezentacją),
• frame (choć i frame nadal istnieje),
• frameset,
• noframes,
• font (używaj CSS-a w celach związanych z prezentacją),
• st ri ke (zależnie od kontekstu używaj s albo de l ),
• tt (używaj CSS-a w celach związanych z prezentacją),
• u (używaj CSS-a w celach związanych z prezentacją) .

To tak naprawdę wszystko, co jest do powiedzenia. Nie będziemy ogłaszać żałoby po tych elementach,
a jeśli obecnie ich używasz, natychmiast przestań!

PRZEPIS DLA POCZĄTKUJĄCYCH


Opakowywanie elementów łączami
Przydatną nową funkcją HTMLS jest możliwość grupowania kilku elementów w j edno łącze. Uzyskujesz
dzięki temu znacznie szerszy obszar do klikania - w przeszłości prawdopodobnie używałeś w tym celu
JavaScriptu albo kombinacji tagów.
Jeśli w HTML 4 tworzyłeś główną stronę bloga albo stronę z aktualnościami zawierającą łącza do wielu
artykułów, być może używałeś kodu podobnego do tego z listingu 2 . 1 3, aby uzyskać możliwość klikania
każdej pozycji.

Listing 2 . 13 . Opakowywanie łączy w HTML 4

<di v c l as s = " arti c l e " >


<h2><a href="art i c l e . htm"><img al t = " m i n i atura artykutu" sre = " t humb . j pg " hei ght=" lOO" w i dt h = " lQO" j>Tytut
�moj ego artykutu</a><jh2>
<p><a href="arti cl e . htm">Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae
�turpi s egestas . </a><jp>
<jd i v>

Listing 2.14 pokazuje, że w HTMLS możesz opakować to wszystko w jeden element a.

Listing 2 . 14. Opakowywanie łączy w HTML5

<art i e l e>
<a href="arti c l e . htm">
<h2>< img al t="mi ni atura artykutu" sre = " t humb . j pg " hei ght = " lOO" w i dt h = " lQO" j>Tytut mojego artykutu<jh2>
<p>Pel l entesque hab i t ant morbi tri s t i que seneetus et netus et mal esuada fames ae turp i s egestas . <jp>
< l a>
<jart i e l e>

Elementem a możesz również opakować art i c l e; wiadomo jednak, że przeglądarki mają z tym pewne
problemy, więc proponujemy, by unikać tego rozwiązania.
66 Rozdział 2. Gru powanie, poziom tekstu i zm iany semantyczne

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Dodawanie informacji semantycznych
za pomocą mikrodanych
Specyfikacja Microdata (mikro dane) pozwala autorom dodawać etykiety do fragmentów treści, by mogły
być odczytywane komputerowo. W teorii użycie formatowania mikrodanych pomaga "maszynom", takim
jak Google, podawać dokładniejsze informacje o stronach.
Możesz traktować mikrodane j ako element niestandardowy i używać go do etykietowania firm, osób,
produktów albo zdarzeń. Atrybuty mikro danych możesz stosować w już istniejących elementach.
Mikrodane mają pięć atrybutów: i temi d, i temprop, i temref, i temscope i i temtype. Listing 2.15 pokazuje
prosty przykład opisania osoby.

Listing 2 . 1 5 . Przykład mikrodanych

<art i c I e i temscope i temtype='' http : //schema . org/Person''>


<hl i temprop= "name">Jan Kowal s k i <j h l>
<p><span i temprop="address" i temscope i temtype= ''http: //schema . org/Postal Address''>Moje mi ejsce zami eszkan i a
�to <span i temprop="addressRegi on">Warszawa< j span> « span i temprop="addressCountry">Pol s ka<jspan» . <jp>
<p>Pracuję j a ko <span i temprop="j obTi t l e " > kosm i c z ny kowal <jspan> w f i rmi e <span
�itemprop="affi l i ati on">Kosm i c z n i Kowal e sp. z o . o . <jspan> Mam równi eż w i t rynę s i ec i ową : <a
�href= '' http : //www . exampl e . com'' i temprop="url ">exampl e . com<ja><jp>
<jart i c I e>

Listing 2.15 pokazuje opis osoby, która nazywa się Jan Kowalski, mieszka w Polsce i pracuje na stanowisku
kosmicznego kowala dla firmy Kosmiczni Kowale sp. z 0 . 0 .
Kiedy w jakimś elemencie jest użyty atrybut i temscope, element ten staje się j ednostką mikro danych.
Atrybut i temprop j est właściwością tej jednostki mikro danych i opisuje, co j est jej treścią. W przykładzie
mamy dwa atrybuty i temscope: jeden dotyczy całego fragmentu (Person), a drugi znajdującego się w nim
adresowego atrybutu i temtype.
Wartości i temprop w przykładzie pochodzą ze strony http://schema.org/, na której podane są nazwy
różnych typów danych (zdarzeń, produktów i tak dalej). Strona została uruchomiona w czerwcu 20 1 1 roku
jako wynik uwspólnienia schematów z Bing, Google i Yahoo!. Zawiera kilka przykładów i olbrzymi wybór
przykładowych danych. Możesz użyć swoich własnych wartości, lecz dla uzyskania spójnych wyników
powinieneś korzystać ze standardowych, uznanych nazw. Google również podaje przykłady i sugestie nazw
pod adresem http://support.google.com/webmasters/bin/answer.py?hl=pl&answer=99170. Używając narzędzia
do testowania opisów rozszerzonych dostępnego wśród narzędzi Google dla webrnasterów, możesz sprawdzić,
jak Google pokazałby Twoje mikrodane na stronie z wynikami wyszukiwania, co pokazano na rysunku 2.6.

Jan Kowalski

Warszawa - kosmiczny kowal - Kosmiczni Kowale sp. z 0 . 0 .


The excerpt from the page will show u p here. The reason we c a n't show tex! from your
webpa ge is because the tex! depend s on the qu ery the u ser types.

Rysunek 2.6. Podgląd wyników wyszukiwania Google

Rysunek 2.7 pokazuje inne informacje na temat treści zgromadzone przez Google.
Stosowan ie WAl-ARIA z HTML5 67

Extracted structured data

Ilem

type: http://schema. org/person


property:
n ame: Jan Kowalski
address: Ilem l
j o bt it l e : kosmiczny kowal
affiliation: Kosmiczni Kowale sp. z 0. 0.

url: example.com

Ilem l

type: http:// s c h ema.o rg/po st al a dd res s


property:
addressregion: Warszawa
addresscountry: Polska

Rysunek 2 . 7 . Dodatkowe informacje zgromadzone przez Google

To był tylko drobny przykład użycia mikro danych. Istnieje mnóstwo innych użytecznych możliwości,
których przykłady znajdziesz na wspomnianych wcześniej stronach Google. Oprócz narzędzi Google
istnieje kilka innych, które mogą pomóc przy tworzeniu mikrodanych: http://foolip.org/microdatajs/live/
i http://microdatajreebaseapps.com/.

I
Uwaga
M icroformats ( m i kroform aty) i R DFa to dwa inne sposoby rozszerzan i a HTML-a o określone inform acje.
Oba mają swoje zalety i wady. W rzeczywistości możesz łączyć m i krod ane z m i kroformata m i , a chociaż
m i krodane powo l i stają się standarde m , m i kroformaty są obecnie bardziej popularne.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Stosowanie WAl-ARIA z HTML5
Web Accessibility Initiatives2 Accessible Rich Internet Applications3 (WAl-ARIA), znana również po prostu
jako ARIA, jest roboczą specyfIkacją (http://www.w3.orglTR/wai-arial), która zwiększa dostępność aplikacji
i stron internetowych. ARIA umożliwia programistom i autorom treści rozwijanie bogatych aplikacji
internetowych i treści, które mogą być rozpoznane i używane przez technologie ułatwień dostępu. Częściej
niż rzadziej technologie ułatwień dostępu nie rozpoznają widżetów, a te zwykle nie są dostępne z klawiatury.
Weź również pod uwagę to, że kiedy treść j est aktualizowana poprzez wywołanie ajaksowe, technologia
ułatwień dostępu nie wie, że treść została zaktualizowana, i w związku z tym nie może o tym poinformować
użytkownika. Chociaż nie będziemy mówić o wszystkich możliwych rozwiązaniach, które oferuj e ARIA,

2 Inicjatywa dostępności do sieci - przyp. tłum.


3 Dostępne bogate aplikacje internetowe - przyp. tłum.
68 Rozdział 2. Gru powanie, poziom tekstu i zm iany semantyczne

opowiemy o fragmencie specyfikacji ARIA zatytułowanym "Landmark Roles"4 i o tym, jak możesz dodać te
nowe role do swojego dokumentu HTML5. Role są obszarami strony używanymi jako nawigacyjne punkty
orientacyjne. Specyfikacja wymienia ich ponad 50 (http://www.w3.orglTR/wai-aria/roles#landmark_roles),
a tutaj wskazujmy te bardziej popularne:
• ro l e= "art i c l e " ,
• ro l e= " banner",
• roi e= " compl ementary " ,
• ro l e= " conten t i n fo " ,
• ro l e = " form",
• ro l e = " head i ng",
• ro l e = "ma i n " ,
• ro l e= "navi gat i o n " ,
• ro l e = " searc h " .

Możesz je łatwo dodać d o swoich znaczników w następujący sposób:


<form rol e= " searc h " >

</form>

Oznacza to, że ten konkretny formularz (na stronie może być kilka formularzy) jest używany do
wyszukiwania.
Przeglądając powyższą listę, możesz zauważyć, że niektóre role w oczywisty sposób nawiązują do nowych
elementów HTML5, a kiedy dodasz je do struktury głównej strony z poprzedniego rozdziału, otrzymasz
układ podobny do tego z rysunku 2.8.
Ponieważ używasz logicznej struktury znaczników wraz z rolami ARIA, pewnego dnia technologia
ułatwień dostępu będzie w stanie łatwo przemieszczać się do określonych obszarów treści strony. Jednak
obecnie czytniki ekranu w ograniczony sposób obsługują nie tylko HTML5, ale również elementy ARIA.
Nadal zaleca się używanie łączy pomijających na początku dokumentu, bardzo często ukrytych za pomocą
CSS-a, które pozwalają osobom nawigującym za pomocą czytnika ekranu, klawiatury albo innej technologii
ułatwień dostępu szybko "przeskoczyć" do ważnych obszarów, zwykle obszaru głównej nawigacji lub głównej
treści. Kod wyglądałby podobnie do tego:
<ul >
<l i ><a href= " #menu " >Przejdi do naw i gacj i </a></l i >
<l i ><a href= " #content " >Przejdi d o treśc i </a></l i >
<fu l >

Jednak przy użyciu ARIA punkty orientacyjne są oznaczone, dzięki czemu użytkownik będzie mógł
krążyć pomiędzy opcjami.
Walidacja HTML5 akceptuje role ARIA. Możesz używać ról ARIA także w HTML 4, ale spowodują one
błąd walidacji.

4 Role (punkty orientacyjne) - przyp. tłum.


Oznaczanie komentarzam i strony z artykułem 69

<header ro le= " banner " >

<nav role = " navigation " >

<artic le ro l e= " main " >

<secti on>

<secti on>

< footer rol e= " contentin fo " >

Rysunek 2.8. Prosty układ witryny z rolami ARIA

Role mogą być również używane w selektorach CSS-owych. Możesz mieć kilka nagłówków albo stopek
na stronie, ale jeśli chcesz nadać odmienny styl nagłówkowi i stopce głównej strony, to możesz się do nich
odwołać w CSS-ie w następujący sposób:
/* nadanie stylów wszystkim nagłówkom */
header { bac kground: red; border: 5px dotted bl ac k ; }
/* nadanie stylu głównemu nagłówkowi. który prawdopodobnie zawiera logo witryny */
header [rol e=banner] { bac kground: bl ac k ; border: 5px sol i d red ; }
/* nadanie stylu wszystkim stopkom */
footer { bac kground: bl ue ; border: 5px dotted green ; }
/* nadanie stylu stopce naszej witryny. która prawdopodobnie zawiera informację dotyczącą praw autorskich */
footer [rol e=content i nfo] { bac kground: green ; border: 5px sol i d bl ue ; }

Nie musisz koniecznie używać CSS-a w ten sposób, ale jest to użyteczna opcja do rozważenia.
Tematów związanych ze specyfikacj ą WAl-ARIA oraz dostępnością HTMLS jest na tyle dużo,
że zachęcamy Cię do dalszej lektury na następujących witrynach:
• http://www.w3.org/TR/wai-aria/,
• http://htmI5accessibility.com/,
• http://www.paciellogroup.com/blog!.

PRZEPIS DLA ZAAWANSOWANYCH


Oznaczanie komentarzami strony z artykułem
Listing 2.16 zawiera kod potrzebny do utworzenia strony zawierającej artykuł z komentarzami (rysunek 2.9).
Użyliśmy w nim kilku nowych technik omówionych w tym rozdziale.
70 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne

Listing 2 . 16 . Artykuł z komentarzami

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< t i t l e>Komentarze na bl ogu<jt i t l e>
<styl e>
[rol e=banner] hl { bac kground: #33 3 ; col or: #fff; padd i ng : 5px ; }
ero l e=ma i n] h l { border-bottom: 2px dotted #333 ; c ol or: #333 ; }
b { fl oat : l eft ; font-fami l y : " Pal at i no Li notype " , Pal at i no , ser i f ; font - s i z e : 2 . 5em; font-styl e : i tal i c ;
�font-wei ght : bol d ; l i ne-he i ght : 1 ; marg i n : O 5px 5px O ; }
<jstyl e>
<jhead>
<body>
<header rol e= " banner">
<hl>Bl og Tomasza<jh l>
<jheader>
<art i c l e rol e= " ma i n " >
<header>
<h l>Tytut mojego artykutu<jhl>
<t i me pubdate date t i me= " 20 l 0 - l 2 - l 2 " >N i edz i e l a , 12 grudni a 20l0<jt ime>
<p><b>P<jb>el l entesque habi tant morbi tri st i que senectus et netus et mal esuada fames ac turp i s egestas .
�Vest i bul um tortor ... <jp>
<jheader>
<sect i on>
<h2>Komentarze<jh2>
<o l reversed>
<l i >
<art i c l e>
<header>
<h3><t i me datet ime= " 20 l 0 - l 2 - l 3T l l : 15Z">20 l 0 - l 2 - l 3 1 1 : 15<j t i me><jh3>
<jheader>
<footer>Komentarz <address><a href= " http: //exampl e . com">Tomasza Kowal s ki ego<ja><jaddress><jfooter>
<p>Co za wspan i aty artyku t ! <jp>
<jart i c l e>
<jl i >
<l i>
<art i c l e>
<header>
<h3><t i me datet ime= " 20l0- l 2 - l 6T l l : 15Z">20 l 0 - l 2 - l 6 1 1 : 15<j t i me><jh3>
<jheader>
<footer>Anonimowy komentarz<jfooter>
<p>To j a k i eś bzdury. <jp>
<jart i c l e>
<jl i >
<jol >
<jsect i on>
<jart i c l e>
<jbody>
<jhtml >

Listing 2 . 1 6 wykorzystuj e kilka ról stanowiących punkty orientacyjne ARIA użyliśmy ich do nadania
-

stylu znacznikom h l . Korzystamy również z elementu b do wyróżnienia pierwszej litery artykułu, dzięki
czemu j est trochę bardziej wyszukana. Kiedy nadajesz style elementom h l oraz b, nie musisz koniecznie
używać powyższego CSS-a, ponieważ istniej ą inne sposoby odwołania się do tych elementów; dobrze jednak
jest znać różne możliwości.
Podsumowanie 71

Blog Tomasza

!.Y.�.�.�..�.?J�g?...�.�.��.���.....................................................................................................................
Niedziela, 12 grudnia 20 l O

ellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas . Vestibulum tortO! quam,
P feugiat vitae, ultricies eget. tempor sit amet, ante. Donec fU libero sit amet quam egestas semper. Aenean ultricies
mi vitae est. Mauris placerat eleifend leo.

Pellentesque habitant morbi ttistique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam,
feugiat vitae, ultricies eget, tempor sit amet. ante. Danec eu libero sit amet quam egeslas semper. Aenean ultricies mi
vitae es't. Mauris placerat e leifend leo.

Komentarze

l. 2010-12-13 1 1 : 15

Komentarz
Tomasza Kowalskiego

Co za wspaniały artykuł!

2. 2010-12-16 1 1 : 15

Anonimowy komentarz

To jakieś bzdury.

Rysunek 2.9. Artykuł z komentarzami

Nowy element t i me został użyty kilkakrotnie, raz w przypadku głównego elementu art i el e z atrybutem
pubdate, a następnie w ramach każdego komentarza. W poprzednim rozdziale przeczytałeś, że komentarz
użytkownika może być umieszczony w elemencie a rt i e l e, więc użyliśmy go tutaj jako tytuł wykorzystując
datę i czas. Moglibyśmy umieścić w tytule autora komentarza, ale nie chcemy mieć w konspekcie powtórzeń,
a używanie daty i czasu gwarantuje unikalność. Wybór należy do Ciebie, za tytuł może posłużyć również
autor komentarza.
Zastosowana została również lista uporządkowana, tak aby każdy komentarz miał numer, co nie tylko
porządkuj e komentarze, ale daje nam również możliwość nadania stylu. Użyliśmy atrybutu reversed
w elemencie o l , ponieważ w tym przypadku chcemy, by ostatni komentarz był umieszczony na początku.
Moglibyśmy potencjalnie dodać przełącznik "posortuj po dacie" oraz za pomocą J avaScriptu dodawać lub
usuwać atrybut reversed. Oczywiście nie musisz robić tego w taki sam sposób, istnieje mnóstwo alternatyw,
a układ komentarzy może sprawić, że będziesz musiał rozważyć inne rozwiązania.

Podsumowanie
W tym rozdziale poznałeś wiele nowych elementów oraz takich, które zostały nieznacznie poprawione
w HTMLS. Nowe elementy, takie jak fi gure i deta i l s, na pewno ułatwią w przyszłości życie programistów,
a dzięki nowym rolom ARIA możesz strukturyzować swoj e dokumenty, nadając im lepszą semantykę
i sprawiając, że są bardziej dostępne. Możesz nawet zamieszczać dodatkowe informacje w swoim HTML-u
przy użyciu mikro danych, tak aby wyszukiwarki miały możliwość udostępniania bogatszych i dokładniejszych
informacji.
72 Rozdział 2 . Gru powanie, poziom tekstu i zm iany semantyczne
3
Obsłu g a prze glądarek w HTML5

C
hociaż specyfikacj e HTMLS i związanych z nim technologii nie są ukończone i caly czas się zmieniają,
wielu spośród dostępnych funkcji możesz już używać. HTMLS jest rozwij ającym się językiem i zawiera
coraz więcej javascriptowych API . Producenci przeglądarek ciężko pracują, by nadążać za rozwojem nowych
specyfikacji. Ze względu na te "ruchome cele" - nowe funkcjonalności i nowe wersje przeglądarek - my,
programiści, możemy się często natknąć na problemy z obsługiwaniem przez przeglądarki pewnych funkcji.
W tym rozdziale dowiesz się, jak radzić sobie z różnicami pomiędzy przeglądarkami i jak wypełniać luki
przy użyciu dostępnych sztuczek, wskazówek i skryptów.

PRZEPIS DLA POCZĄTKUJĄCYCH


Obsługa Internet Explorera
Jeśli wypróbowałeś kod z poprzednich rozdziałów w Internet Explorerze (IE) 7, 8 lub nawet 6 (naprawdę
mamy nadzieję, że nie jesteś zmuszony do obsługiwania IE6), to mogłeś zauważyć drobny problem - strony,
które utworzyłeś w przepisach, mogly nie wyglądać tak, j ak powinny. Dzieje się tak, ponieważ kod, który
do tej pory prezentowaliśmy, działa bezpośrednio tylko w następujących głównych przeglądarkach: Firefox,
Chrome, Safari, Opera i Internet Explorer 9 (IE9). Nie oznacza to jednak, że IE7 i IE8 zupełnie nie obsługują
HTMLS; dostępne są różne API HTMLS, które w tych przeglądarkach działają, takie j ak Drag and Drop!
oraz ContentEd; tab l e.

Użycie J av aS criptu d o u zyskania kompatyb ilnoś ci z HTMLS


IE6, 7 i 8 domyślnie nie rozpoznają nowych elementów HTMLS, takich jak art; c l e lub sect ; on, w związku
z czym nie zostaną do nich zastosowane style. Jeśli niczego z tym nie zrobisz, strona prawdopodobnie
nie będzie wyglądać tak, j ak byś chciał. Żeby IE rozpoznał te elementy, możesz je utworzyć za pomocą
JavaScriptu, jako część DOM, a następnie nadać im odpowiedni styl, tak jak w tym przykładzie:
<scri pt>
document . createEl ement ( ' art i c l e ' ) ;
document . createEl ement ( ' sect i on ' ) ;
IIż tak dalej
<jscri pt>

! Przeciągnij i upuść - przyp. tłum.


74 Rozdział 3 . Obsługa przeglądarek w HTML5

Oczywiście pisanie tego dla każdego nowego elementu HTMLS byłoby nieco nużące i istnieje ryzyko,
że coś byś pominął. Na szczęście możesz używać HTMLSShiv (http://code.google.com/p/htmI5shiv), napisanego
przez Remy'ego Sharpa, zawieraj ącego wszystkie nowe elementy oraz dodatkowo kawałek JavaScriptu,
zwany lE Print Protector, który pomaga lE poprawnie wyświetlać elementy HTMLS. HTMLSShiv j est tak
zaprojektowany, by umożliwić wersjom przed lE9 rozpoznawanie elementów HTMLS i pozwolić na nadawanie
im stylów przy użyciu CSS-a.
Chociaż skrypt zawiera kod warunkowy, który może być uruchamiany tylko w lE, możesz również
opakować go we własny warunkowy komentarz, tak by tylko lE8 i starsze wersje przeglądarki pobierały
i uruchamiały kod. Skrypt musi być umieszczony w znaczniku head oraz przed odwołaniami do arkuszy
stylów, tak jak to pokazano na listingu 3 . 1 . Od Ciebie zależy, czy wolisz serwować plik html5shiv lokalnie,
czy linkować bezpośrednio do pliku dostępnego ze strony http://code.google.com. Udostępnianie pliku lokalnie
zapobiegnie zarówno błędom nieznalezienia zasobu ("resource not found"), spowodowanym j akimiś
problemami ze zdalnym dostępem, jak i błędom wynikającym z przyszłych modyfIkacji javascriptowego pliku.

Listing 3 . 1 . Użycie HTML5Shiv

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< l o - C i f l t J E 9J >
<scri pt src= " http : //htm1 5sh i v . googl ecode . com/svn/trunk/htm1 5 . j s "><jscri pt>
< ! [end i f] -->
< 1-- umieść CSSpo shivie _ o >
<jhead>

U zyskiwanie kompatybilnoś ci CSS-a


Teraz, kiedy lE może j uż obsługiwać nowe elementy HTMLS, należy j eszcze sprawić, by prawidłowo
interpretował CSS. Większość współczesnych przeglądarek wie, j ak domyślnie obsługiwać nowe elementy
i pokazywać je bez potrzeby określania dla nich stylów; aby się jednak upewnić, że lE będzie je pokazywał
poprawnie, dla swoich nowych elementów powinieneś ustawić d i spl ay : b l oek, tak j ak to pokazano na
listingu 3.2.

Listing 3.2. Ustawianie nowych elementów w CSS-ie

art i c l e , as i d e , detai l s , fi gcapt i on , fi gure , footer, header , hgroup , nav, sect i on


{
di spl ay: bl oek;
}

Jeśli stosujesz resetowanie CSS-a, możesz włączyć kod z listingu 3.2 albo jeden z dostępnych obecnie
resetów CSS-a, które zawierają poprawki HTMLS:
• http://meyerweb.com/eric/thoughts/20 1 1/01/03/reset-revisited,
• Modernizr (który omówimy dalej w tym rozdziale),
• http://htmI5doctor.com/html-5-reset-stylesheet.
Testowanie działan i a nowych możl iwości HTML5 75

Szablony ( boilerplates)
Czy jesteś nowicjuszem, czy weteranem w branży tworzenia stron i aplikacji internetowych, nauczyłeś się
pewnych praktyk programistycznych oraz uzbierałeś różne wycinki kodu, które ponownie wykorzystujesz
w prawie każdym projekcie. Jeśli na zorganizowanie tych komponentów poświęciłeś dużo czasu, możesz
mieć nawet swój własny szablon, na który składa się szereg folderów i plików, pozwalaj ących Ci na szybkie
rozpoczęcie projektu. Istnieje kilka popularnych szablonów, które są chętnie używane przez społeczność
sieciową: http://htmI5boilerplate.com i http://htmI5reset.org. Dodatkowo szablony te zostały już rozszerzone:
możesz wygenerować wzorce dla Twojej konkretnej strony internetowej albo aplikacji poprzez witryny takie
jak http://www.initializr.com. które wykorzystują HTMLS Boilerplate.
Strony te, a HTMLS Boilerplate w szczególności, zawieraj ą mnóstwo dokumentacji, instrukcji oraz
wskazówek i sztuczek odnośnie do Twojego projektu internetowego, w tym kod HTML, skrypty, CSS
i techniki optymalizacji.
Aby używać tych szablonów sensownie, nie wystarczy tylko je ściągnąć i stosować, ale trzeba zrozumieć,
co w nich j est, i dodawać bądź usuwać rzeczy, których nie potrzebujesz albo z którymi się nie zgadzasz,
do czego masz zresztą prawo . Dzięki szablonom możesz po prostu rozpocząć proj ekt i skorzystać z wysiłku
wielu umysłów pracujących nad tym, by dostarczyć strukturę działania i poprawną obsługę HTMLS w różnych
przeglądarkach. Możesz nawet ostatecznie utworzyć swój własny, odpowiednio przystosowany szablon
w oparciu o właściwości uznane przez Ciebie za ważne.

Wskazówka
H T M L5 Bo ilerplate ma długą l istę możl iwości do wykorzystan i a na początku Twojej pracy nad
projektem . Szablony zawierają modele zgodności przeglądarek, u możl iwiają obsługę HTML5
w starszych przeglądarkach , ułatwiają projektowan ie dla u rządzeń m o b i l nych , a nawet oferują
s krypt optymalizacj i budowan i a . Zalecamy, abyś wypróbował pełen zbiór możl iwości pod adresem
http://htm/5boi/erp/ate.com. A jeś l i potrzebujesz na począte k gotowego szablonu, wypróbuj
http://www.initia/izr.com. który za pomocą HTML5 Boilerplate wygeneruje d l a C iebie wzorce stro n .

PRZEPIS DLA POCZĄTKUJĄCYCH


Testowanie działania nowych możliwości HTML5
Wcześniej dowiedziałeś się, jak zmusić przeglądarki do rozpoznawania nowych elementów HTMLS oraz
jak używać CSS-a, by były poprawnie wyświetlane. Jednak nie wszystkie przeglądarki obsługuj ą nowe
możliwości HTMLS, takie j ak canvas albo geo l ocat; on. Chociaż nadal trzeba czasem trochę "poszperać"
w przeglądarce, by zachowywała się w określony sposób, to nie jest to zalecane podej ście, a biorąc pod
uwagę możliwości HTMLS i szybkość, z jaką przeglądarki są rozwij ane, lepiej używać metod wykrywania
obsługi poszczególnych możliwości HTMLS. Wykrywanie działania możliwości wymaga JavaScriptu,
więc musisz się upewnić, że Twój kod HTML jest wystarczająco dobry, by użytkownicy mający starsze
przeglądarki i wyłączony JavaScript mogli nadal korzystać z Twojej strony.
Obsługiwanie przez przeglądarkę canvas i geo l ocat; on, które należą zarówno do elementów HTMLS,
jak i javascriptowych API, można na ogół sprawdzić za pomocą JavaScriptu. Na listingu 3.3 pokazaliśmy,
jak wykryć, czy w przeglądarce jest dostępne Geolocation API .
76 Rozdział 3 . Obsługa przeglądarek w HTML5

Listing 3 . 3 . Testowanie obsługi geolokalizacji

< ! DOCTYPE html >


<html >
<head>
<meta charset = " UT F-8" j>
<t i tl e>3 . Sprawdzan i e geo l o kal i zacj i <jt i t l e><scri pt>
i f (navi gator . geol ocation) {
a l ert ( ' Geo l oka l i zacja j est obstugi wan a . ' ) ;
else {
al ert ( ' Geol okal i zacja n i e j est obs tug i wana . ' l ;

<jscri pt>
<jhead>
<body>
<jbody>
<jhtml >

J ak pokażemy w tej książce, za pomocą javascriptowych API można dość łatwo sprawdzić obecność
danego API. Jeśli j ednak próbujesz sprawdzić w przeglądarce dostępność elementu HTMLS, takiego j ak
element canvas, proces jest trochę bardziej złożony. Najczęściej tworzy się taki element, a następnie próbuje
pobrać j ego domyślną metodę albo atrybut. Jeśli zwróconą wartością j est nul l albo undefi ned, oznacza to,
że element nie jest obsługiwany przez przeglądarkę. Listing 3.4 pokazuje przykład sprawdzenia, czy element
canvas jest obsługiwany.

Listing 3.4. Sprawdzanie działania elementu canvas

< ! DOCTYPE html >


<html >
<head>
<meta charset = " UT F-8" j>
< t i t l e>3 . Sprawdzan i e canvas<jt i t l e>
<scri pt>
i f (document. create El ement ( ' canvas ' ) . getContext)
al ert ( ' El ement canvas j est obstugi wany. ' l ;
else {
al ert ( ' El ement canvas n i e j est obs tug i wany . ' ) ;

<jscri pt>
<jhead>
<body>
<jbody>
<jhtml >

Najpierw utworzyłeś nowy element typu canvas, po czym sprawdziłeś, czy udostępnia on metodę
getContext. Metody elementu canvas poznasz w dalszej części książki, ale w tym punkcie wystarczy wiedzieć,
że metoda getContext j est nieodzowną i domyślną metodą każdego obiektu canvas. Jeśli zatem metoda
getContext nie istniej e w tym elemencie, to sam element nie istniej e i canvas nie j est obsługiwane przez
tę przeglądarkę·
Wykorzystanie jQuery do zastąpienia kalendarza 77

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Wykorzystanie jQuery do zastąpienia kalendarza
w rozdziale 5. poznasz wszystkie nowe możliwości dostępne w HTML5 dotyczące formularzy, ale już teraz
zwięźle wprowadzimy j edną z tych nowych możliwości - mowa o typie wejściowym date. Typ wejściowy
date wyświetla użytkownikowi widżet kalendarza, jak pokazano na rysunku 3 . 1 . W chwili pisania tej książki
działa on tylko w przeglądarkach Opera, iOS Safari oraz Opera Mobile, chociaż w innych przeglądarkach
zachowuje się nieźle, pokazując po prostu zwykły element ; nput, w którym użytkownik może wpisać datę.
My jednak chcemy, by widżet kalendarza pojawiał się we wszystkich przeglądarkach.

Którego dnia wyj eżdżasz? � �

0 Paź ·· G � �
Pn WI Śr Cz PI Sa N

1 2 3 4 5 6 7

8 9 10 1 1 12 13 14

15 16 17 18 19 20 2 1

2 2 23 2 4 25 26 27 2 8

29 30 3 1 2 3 4

5 6 7 9 10 1 1

Dzisiaj I
Rysunek 3 . 1 . Element <input type= "date "j> wyświetlony w przeglądarce Opera 1 1 . 61

Poniższy kod HTML wyświetla kontrolkę wprowadzania daty w przeglądarce Opera:


<form>
<l abel for= " date">Którego dni a wyj eżdżasz?</l abe l >
<i nput req u i red type= " date" i d= " date" name= " date" />
<i nput type= " s ubmi t " val ue= "Wyśl i j " />
</form>

Przy użyciu JavaScriptu należy utworzyć element w DOM, dodać do niego atrybut typu date, a następnie
przetestować, czy przeglądarka go obsługuje. Jeśli tak, nie musisz nic robić. Jeśli jednak przeglądarka go nie
obsługuje, musisz dołożyć widżet kalendarza. Rysunek 3.2 pokazuje kalendarz w Internet Explorerze, a na
listingu 3.5 widać tworzący go kod. W tym przykładzie użyliśmy j Query, by dostarczyć funkcjonalności
kalendarza, więc dla ułatwienia podlinkowaliśmy bezpośrednio potrzebne pliki j Query.

Listing 3 . 5 . Użycie kalendarza jQueryjako kalendarza rezerwowego

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta charset = " utf-8">
< t i t l e>Sprawdzan i e dz i afan i a typu wej ś c i owego date</t i t l e>
<scri pt src= ''http : //aj ax . googl eapi s . com/ajax/l i bsjj query/ 1 . 4 . 4jjquery. mi n . j s "></scri pt>
<scri pt src= ''http : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7 jjquery-u i . mi n . js "></scri pt>
<scri pt src= ''http : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7/ i 18njj query. u i . datepi c ker-p l . js "></scri pt>
<l i nk re l =" styl esheet " href= " htt p : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7 /themes/basejj query-ui . css"
4-med i a = " screen" j><scri pt>
78 Rozdział 3 . Obsługa przeglądarek w HTML5

i l

Oc) I S http:// localhost/listing.3.5.html


Którego dnia wyjeżdżasz? J�
rr=======�======�
Il O Październik 2012 O

Pn Wt Śr Cz Pt 50 N

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

Rysunek 3 . 2 . Internet Explorer z rezelWowym kalendarzem jQuery

$ (funct i on ( ) {
function i nputSupport () {
var i nput = document . createEl ement ("i nput") ;
i nput . setAttri bute ( "type " , "date") ;
var val = (i nput . type ! == "text") ;
del ete i nput;
return val ;
}
i f ( ! i nputSupport O I I ($. browser.webkit» {
Ilalert("typ wejściowy date niejest wspierany");
$ ( ' i nput [type=date] ' ) . datepi cker({
dateFormat : ' yy-mm-dd ' II ten samformat co w specyfikacji HTML5
}) ;
}
});
<jscri pt>
<jhead>
<body>
<form>
<l abel for= " date" >Którego dni a wyj eżdżasz?<jl abel > < i nput requ i red type= " date" i d = " date" name = " date" j>
< i nput type= " subm i t " val ue= "Wyśl i j " j>
<jform>
<jbody>
<jhtml >

Ci Z Was, którzy mają sokole oko, na pewno zauważyli następujący kawałek JavaScriptu: ($ . browser. webki t) .
Uwzględniliśmy go na listingu 3.5, ponieważ - co jest bardzo irytujące - przeglądarki Web Kit wykrywajq
<i nput type= "date ">, ale obecnie nic z nim nie robią. To samo dzieje się z typami wejściowymi ema i l , number,
tel oraz url . A zatem ten mały kawałek JavaScriptu pozwala się upewnić, że widżet kalendarza j Query działa
w Safari i Chrome. Mamy nadzieję, że wkrótce przeglądarki oparte na silniku Web Kit będą domyślnie robiły
coś fajnego z tymi typami wejściowymi, tak byśmy mogli pozbyć się kodu wykrywania przeglądarki.
Kod javascriptowy z listingu 3.5 służy do wykrycia obsługi tylko typu danych date. Jeśli chcesz wykrywać
inne typy, musisz wprowadzić drobne poprawki do poprzedniego kodu, tak abyś mógł testować kilka typów
elementu i nput. Spójrz na przykład na listing 3.6.
Wykorzystan ie b i b l ioteki Modernizr do wykrywan ia możl iwości 79

Listing 3 . 6 . Wykrywanie różnych typów wejściowych

$ (funct i on ( ) {
funct i on i nputSupport ( i nputType) {
var i nput = document . createEl ement ( " i nput " ) ;
i nput . setAttri bute ( " type " , i nputType) ;
var val = ( i nput . type ! == " text " ) ;
del ete i nput ;
return va l ;

i f ( ! i nputSupport ( " date" ) )


al ert ( " typ wej ś c i owy date n i e j est obs tugi wany" ) ;
l/zrób zamiast tego coś innego

i f ( ! i nputSupport ( " ema i l " ) ) {


al ert ( " typ wej ś c i owy ema i l n i e j est obstugi wany" ) ;
l/zrób zamiast tego coś innego
}
});

Jeśli masz niewielką witrynę, zawierającą tylko kilka nowych elementów HTMLS, którą chcesz udostępnić
dla wszystkich przeglądarek, opisane przed chwilą podejście może być odpowiednie, ponieważ jest ono
dość poręczną poprawką. Jeśli jednak musisz wykrywać, czy przeglądarka obsługuje jakieś funkcje, w wielu
miejscach kodu, stanie się to nużące, a w związku z tym możesz rozważyć używanie javascriptowej biblioteki,
takiej j ak Modernizr.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Wykorzystanie biblioteki Modernizr
do wykrywania możliwości
Modernizr jest javascriptową biblioteką (znajdującą się pod adresem http://modernizr.com/), która wykrywa,
jakie możliwości HTMLS i CSS3 przeglądarka obsługuje, oraz ułatwia programistom testowanie i kodowanie
dla przeglądarek, które nie wspierają niektórych spośród nowych technologii. W poprzednich przykładach
użyliśmy standardowego JavaScriptu do sprawdzania obsługi nowych możliwości HTMLS, lecz dzięki
bibliotece Modernizr takie testowanie staje się niezwykle łatwe.
Chociaż Modernizr wykonuje pracę związaną z wykrywaniem funkcji HTML za Ciebie, to jednak nie
uzupełnia luk i nie dodaje za Ciebie brakującej funkcjonalności. Mimo wszystko jest to potężny kawałek
kodu, którego istnienia programiści powinni być świadomi, tak by trzymać go w pogotowiu w swoim
arsenale. Teraz, w wersji 2, biblioteka Modernizr skupia się na wykrywaniu CSS3, obsłudze znaczników
HTMLS i javascriptowych API. Dla każdego z tych obszarów biblioteka Modernizr ma specyficzne właściwości,
do których można uzyskać dostęp po inicjalizacji. Można ich używać do dynamicznego zmieniania kodu
źródłowego oraz obsługiwania różnych przeglądarek i ich sposobów działania. Następująca lista zawiera
niektóre spośród tych właściwości:
• Geolocation API,
• 10calStorage,
80 Rozdział 3 . Obsługa przeglądarek w HTML5

• sessionStorage,
• Drag and Drop,
• History Management,
• applicationCache,
• Canvas,
• Web Sockets,
• Web Workers,
• Web SQL Database,
• Input Types,
• Input Attributes.

Biblioteka Modernizr (albo mikrobiblioteka, jak chcą jej autorzy) jest prosta w użyciu i została wbudowana
w wiele większych i mniejszych stron na całym świecie. Dopóki wszystkie przeglądarki nie będą jednolicie
obsługiwać wszystkich możliwości HTMLS, CSS3 i javascriptowych API, będziemy potrzebować takich
narzędzi jak Modernizr.
J ak się jej w takim razie używa? By wygenerować swój plik, musisz wybrać spośród różnych cech HTMLS
te, których obecność chcesz wykryć. Wybranie tylko kilku z nich jest korzystne, ponieważ pozwala utrzymać
mały rozmiar pliku i zmusza Cię do myślenia bardziej konkretnie o funkcjach, jakie będzie posiadać Twoja
strona internetowa. Najpierw więc generujesz plik ze strony oraz, jak pokazano na listingu 3.7, dołączasz link
wewnątrz elementu head i dodajesz klasę no-j s do znacznika html .

Listing 3 . 7 . Konfiguracja biblioteki Modernizr

< ! DOCTYPE html >


<html cl ass="no-js" l ang= " p l " >
<head>
<meta charset = " utf-8">
< t i t l e>Użyjmy b i b l i oteki Moderni zr<jt i t l e>
<script src="modern i z r . j s "></scri pt>
<jhead>
<body>
<jbody>
<jhtml >

Skrypt Modernizr po dołączeniu uruchomi się automatycznie i zastąpi atrybut klasy no-j s dodany do
elementu html . Skrypt dynamicznie dołączy klasy CSS dotyczące tych funkcjonalności, które przeglądarka
obsługuje, a także tych funkcjonalności, których przeglądarka nie obsługuje. Poniższy kod pokazuje znacznik
html po tym, jak skrypt Modernizr został uruchomiony i zastąpił atrybut c l ass w Chrome w wersji 8. Jak widać,
przeglądarka Chrome 8 obsługuj e wiele atrakcyjnych funkcji CSS3 i HTMLS, ale nie wspiera na przykład
WebGL, w związku z czym została dodana klasa no-webgl .
<html l ang= " p l " c l as s = " j s fl exbox canvas canvastext no -webgl no-touch geol ocat i on postmessage websql database
�no - i ndexeddb hashchange h i s tory draganddrop websockets rgba hs l a mul t i pl ebgs bac kgrounds i ze borderimage
�borderra d i us boxshadow textshadow opac i ty cssan imat i ons csscol umns cssgradi ents cssrefl ect i ons csstransforms
�no-csstransforms3d csstrans i t i ons fontface v i deo aud i o l ocal storage sess i onstorage webworkers
�appl i cat i oncache svg i nl i nesvg smi l svgcl i ppat h s " >
Wykorzystan ie b i b l ioteki Modernizr do wykrywan ia możl iwości 81

Na listingu 3.8 pokazaliśmy, j ak sprawdzić, czy obsługiwane jest Session Storage API2, ilustrując, w jaki
sposób Modernizr ujawnia w tagach CSS-a informacj e o działaniu różnych funkcji. Jeśli przechowywanie
danych w pamięci sesji jest dostępne, zostanie użyta klasa stylów . ses s ; onstorage, a jeśli nie jest obsługiwane,
używana będzie klasa stylów . no-ses s ; onstorage.

Listing 3.8. Opcje CSS-a przy użyciu biblioteki Modernizr

< ! DOCTYPE html >


<html cl ass="no-js" l ang= " p l " >
<head>
<meta charset = " UT F-8" j>
< t i t l e>3 . Modern i zr CSS<jt i t l e>
<styl e>
d i v . storageNo , d i v . storageYes { di spl ay: none }
.no-sessi onstorage d i v . storageNo { di spl ay: bl oc k
. sessi onstorage d i v . storageYes { d i spl ay : bl oc k }
<jstyl e>
<scri pt src = " modern i z r . j s " type= " textjjavascri p t " ><jscri pt>
<jhead>
<body>
<d i v c l ass= " storageNo " >Ses s i onStorage n i e j est obs ług i wane. <jd i v>
<d i v c l ass= " storageYes ">Sessi onStorage j est obs ług i wane . <jd i v>
<jbody>
<jhtml >

w powyższym przykładzie odpowiedni blok d; v zostanie wyświetlony w zależności od tego, czy wspomniane
API jest obsługiwane. Możesz używać tego sposobu przy dowolnym spośród j avascriptowych API. Spis
wszystkich właściwości udostępnionych w obiekcie Modernizr jest dostępny na stronie http://modernizr.com,
w części zawierającej dokumentację.
Choć pokazany sposób jest użyteczny przy programowaniu w języku CSS, Modernizr zawiera mnóstwo
właściwości javascriptowych, które mogą być używane do wykrywania możliwości HTMLS. Na listingu 3.9
informujesz użytkownika o tym, czy przechowywanie danych w pamięci sesji jest dostępne, w zależności od
wartości jednej z właściwości obiektu Modern ; zr.

Listing 3.9. Wykrywanie możliwości HTML5 przy użyciu obiektu Modernizr oraz JavaScriptu

< ! DOCTYPE html >


<html cI ass=" no-j s " >
<head>
<meta charset = " UT F-8" j>
< t i t l e>3 . Modern i zr JS<jt i t l e>
<scri pt src = " modern i z r . j s " type= " textjjavascri p t " ><jscri pt>
<scri pt>
i f (Moderni zr . sessi onstorage) {
al ert ( ' Sess i onStorage j est obs ług i wane . ' l ;
else (
al ert ( ' Sess i onStorage n i e j est obs ług i wane. ' l ;

<jscri pt>
<jhead>

2 Obsługujące przechowywanie danych w pamięci sesji - przyp. tłum.


82 Rozdział 3. Obsługa przeglądarek w HTML5

<body>
</body>
</html >

Modernizr możesz również używać łącznie z j Query i innymi bibliotekami. Na listingu 3.5 zastosowaliśmy
javascriptowe rozwiązanie do udostępnienia widżetu kalendarza przeglądarkom, które nie obsługuj ą typu
date elementu ; nput z HTMLS. Na listingu 3 . 1 0 odtworzyliśmy ten przykład, używając biblioteki Modernizr
jako narzędzia wykrywającego.

Listing 3 . 10 . Rezerwowy kalendarzjQuery z użyciem biblioteki Modemizr

< ! DOCTYPE html >


<html cl ass="no-js" l ang= " p l " >
<head>
<meta charset = " utf-8">
< t i t l e>Użyjmy b i b l i oteki Moderni zr</t i t l e>
<scri pt src = " modern i z r2 . j s " ></scri pt>
<scri pt src= " http : //aj ax . googl eapi s . com/ajax/l i bsjj query/ 1 . 4 . 4jjquery. mi n . j s "></scri pt>
<scri pt src= "htt p : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7 jjquery-u i . mi n . js "></scri pt>
<scri pt src= "htt p : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7/ i 18njj query. u i . datepi c ker-p l . js "></scri pt>
<l i nk re 1 = " styl esheet" href= "htt p : //aj ax . googl eapi s . com/ajax/l i bsjj queryu i / 1 . 8 . 7 /themes/basejjquery-u i . c s s "
4-med i a = " screen" />
<scri pt>
$ (funct i on ( ) {
Iljeśli przeglądarka nie wspiera typu wejściowego
i f ( ! Modern i z r . i nputtype s . date) {
$ ( ' i nput [type=date] ' ) . datep i c ker ( {
date Format : ' yy-mm-dd ' II ten samformat co w specyfikacji HTML5
}) ;
}
});
</scri pt>
</head>
<body>
<form>
<l abel for= " date" >Którego dni a wyj eżdżasz?</l abel >
< i nput requ i red type= " date" i d= " date" name= " date" />
< i nput type= " subm i t " val ue= "Wyśl i j " />
</form>
</body>
</html >

Zazwyczaj nie musisz używać początkowego fragmentu J avaScriptu $ (funet; on () { } ) , j ak to zrobiliśmy


na listingu 3.10, ale w tym przykładzie korzystamy z kalendarza jQuery, więc jest on potrzebny. I to już wszystko
na ten temat. Jest to naprawdę łatwe; w dokumentacji Modernizr pod adresem http://modernizr.com/docs
znaj dziesz dziesiątki opcji, w tym możliwość rozszerzania biblioteki o nowe testy.

Polyfilling ( wielowypełnianie)
Możliwość wykrywania, jakie cechy HTMLS są obsługiwane - albo za pomocą Twojego własnego kodu,
albo przy użyciu biblioteki Modernizr - to tylko połowa sukcesu. Koniec końców celem jest obsługiwanie
funkcjonalności HTMLS w różnych przeglądarkach i wersjach przeglądarek, które użytkownicy wykorzystuj ą
Polyfi l l ing (wielowypełn ianie) 83

do odwiedzania tworzonych przez Ciebie aplikacji internetowych i stron. By umożliwić wykorzystywanie


przeglądarek, które nie obsługują konkretnej funkcjonalności HTMLS, musiałbyś użyć zewnętrznej biblioteki,
która zawiera kod pozwalający różnym przeglądarkom posiadać te same funkcjonalności. Kod albo biblioteka
kodu, która umożliwia obsługiwanie danej możliwości w przeglądarkach, j est zwana wielowypełniaczem3•
Przykładem wielowypełniacza mogłaby być biblioteka, która udostępnia obsługę canvas w Internet Explorerze,
gdyż Internet Explorer nie obsługuj e elementu canvas bezpośrednio, ale ma swoją własną funkcj onalność
do rysowania, taką jak Silverlight. Dobrze byłoby móc rozwij ać kod z użyciem nowych API, ale mimo to
oferować taki sam sposób działania w przeglądarkach, które nie obsługuj ą ich bezpośrednio. Widzieliśmy
przykłady używania j avascriptowej biblioteki, takiej jak j Query, do łatania niektórych luk, ale nie istnieje
jedna biblioteka, która naprawi wszystko.
Nie tylko starsze przeglądarki mają dziury w obsłudze nowych możliwości HTMLS; nowe przeglądarki
również nie implementują jeszcze powszechnie całego standardu HTMLS. A zatem korzystając z własnego
kodu wykrywającego albo z Modernizr, będziesz musiał załadować odpowiednią bibliotekę wielowypełniacza
do zapewnienia tego samego poziomu obsługi funkcji HTMLS w wielu przeglądarkach. Modernizr udostępnia
usługę ładowania skryptów bezpośrednio poprzez metodę Modern i z r . l oad, która testuje konkretne możliwości
i w rezultacie może dynamicznie załadować wielowypełniacz, tak jak zostało to pokazane poniżej :
Modern i z r . l oad ( {
tes t : Moderni zr. canvas ,
yep : ' myCanvas . j s ' ,
nope: ' myCanvasPolyfi l l er. j s '
});

Wskazówka
Metoda Modern i z r . l oad jest w rzeczywistości a l i asem polece n i a dołączonej zewnętrznej b i b l ioteki
javascri ptowej o n azwie yepnope.js, którą możesz znaleźć pod adresem http://yepnopejs.com.
B i b l ioteka jest przeznaczona do tego , by być szyb k i m , warun kowym i m aj ącym m ały rozm iar
programem ładującym zasoby. Może być używana oddzie lnie albo poprzez al ias metody ładuj ącej
b i b l ioteki Modern izr. Użycie javascri ptowej b i b l ioteki yepnope jest bardzo korzystne, gdyż pozwala
warun kowo ładować CSS albo J avaScri pt dzięki testowan i u różnych rodzajów o b i e któw d anych
- od napisów do tablic.

Dostępnych j est wiele już napisanych wielowypełniaczy dla mnóstwa komponentów w różnych
przeglądarkach; ich długą listę możesz znaleźć pod adresem https://github.com/Modernizr/Modernizr/
wiki/HTML5-Cross-browser-Polyfills. Ta pożyteczna lista jest prowadzona przez Paula Irisha, głównego
programistę biblioteki Modernizr, i zawiera rezerwowe implementacje dla różnego rodzaju możliwości,
w tym canvas, v i deo, geo l ocat i on, forms i wielu innych.

Uwaga
Zastosowan i e różnych wielowypełn i aczy, s h i mów i b i b l i otek umożliwiaj ących obsługę możl iwości
HTML5, które obecnie mogą nie być dostępne w n i e których przeglądarkach , jest świetnym sposobem
dostarczan i a jednol itego zachowan i a użytkown i kom wykorzystującym różne przeglądarki . Jeś l i jed nak
mus isz ładować d użo wielowypełn iaczy, może to m ieć negatywny wpływ n a wydajność. Dłuższy czas
ładowania powinien być zrównoważony uzys kaną funkcjonalnością i poziomem obsługi poszczególnych
funkcj i .

3 Ang. polyfill lub polyfiller - przyp. tłum.


84 Rozdział 3 . Obsługa przeglądarek w HTML5

Użyteczne strony sprawdzające HTML5


Omówiliśmy techniki i skrypty pomagaj ące obsługiwać różne zachowania przeglądarek, a poniżej
prezentujemy podręczną listę stron - dowiesz się z nich, które przeglądarki obsługują które możliwości.
Jeśli nie j esteś pewien czegoś związanego z j akąś szczególną możliwością, możesz spróbować odwiedzić
jedną z tych stron:
• http://fmbip.com - udostępnia szczegółowe informacje na temat przeglądarki, w której
uruchomiłeś stronę, dotyczące CSS3, HTMLS, Forms 2 .0, testów selektorów CSS3, obsługi
skryptów i adresu lP. W rzeczywistości strona wykorzystuje bibliotekę Modernizr, by dostarczyć
wielu spośród pokazywanych informacji.
• http://caniuse.com - udostępnia tablicę zgodności różnych przeglądarek z HTMLS, API J avaScriptu
oraz CSS3, która może być w razie potrzeby filtrowana.
• http://htmI5test.com - testuj e zbiór różnych możliwości HTMLS i przyznaje punkty Twoj ej
przeglądarce w zależności od tego, co jest przez nią obsługiwane. W ten sposób można porównać
różne przeglądarki.
• http://htmI5readiness.com - wizualizacja stanu różnych przeglądarek w powiązaniu z możliwościami
HTMLS.

Podsumowanie
W tym rozdziale poznałeś niektóre metody radzenia sobie ze starymi wersjami przeglądarek i brakami
w przeglądarkach w odniesieniu do HTMLS. Wprawdzie istnieje już szeroki zakres wsparcia różnych
CSS-owych, HTML-owych i javascriptowych dodatków tworzących HTMLS, ale jest to standard, który
jest nadal rozwijany. Dowiedziałeś się również o resetach CSS-a i szablonach, które zawierają nowoczesne
i aktualne narzędzia, wskazówki oraz techniki tworzenia stron internetowych.
Poznałeś możliwości użycia biblioteki Modernizr, potężnego skryptu wykrywającego cechy HTMLS,
umożliwiającego Ci oferowanie rezerwowych rozwiązań dla przeglądarek, które j eszcze nie obsługują
niektórych j ego nowych cech. Nie będziemy pokazywać w książce, j ak używać biblioteki Modernizr,
ponieważ Ty sam musisz zdecydować, czy w ogóle ją stosować na swoich własnych stronach, i ponieważ
próbuj emy pokazać tu podstawowe sposoby rozwiązywania problemów. Zachęcamy jednak do korzystania
z tej biblioteki albo jednego z innych narzędzi wspomnianych w tym rozdziale, gdyż przyniesie Ci to wiele
korzyści podczas pisania kodu HTMLS.
4
Nowe techniki dotyczące układu
i stylizacji w CSS3

CSS
poziomu 3 (CSS3) jest najnowszym wydaniem specyfikacji CSS. Zostało ono oparte na poprzednich
wersjach, zawiera j ednak nowe możliwości, które możesz zaimplementować w najnowszych
przeglądarkach, by poprawić wygląd, łatwość użycia, dostępność i wydajność swoich stron internetowych.
CSS to nie HTMLS - wiemy o tym i Ty także powinieneś to wiedzieć, ale dołączyliśmy ten rozdział, ponieważ
często współdziałaj ą one ręka w rękę, a my chcemy pokazać Ci tylko niektóre z możliwości dostępnych
w CSS3, abyś mógł nadać Twoim witrynom HTMLS fantastyczny wygląd. W tym rozdziale powiemy sobie
o witrynach reaguj ących na zmiany, proj ektowanych z użyciem Media Queries, zewnętrznych czcionek,
gradientów, przejść i transformacji oraz, na końcu, animacji. Nie opisujemy wszystkich nowych możliwości
CSS3; odkrywamy j edynie rąbek tego, co jest dostępne.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Tworzenie dostosowującego się projektu
za pomocą CSS3 Media Queries
Coraz częściej z internetu korzysta się przy użyciu urządzeń mobilnych, a niektórzy szacuj ą, że taki sposób
dostępu prześcignie przeglądanie z komputerów stacjonarnych w ciągu pięciu lat. CSS3 Media Queries1 daje
Ci możliwość dostosowania strony do określonych rozdzielczości, a nawet orientacji ekranu, tak że możesz
obsługiwać smartfony, takie jak iPhone, albo telefony z systemem Android, jak również nowe tablety, takie
jak iPad, i to wszystko za pomocą CSS-a. Tabela 4.1 pokazuje, które wersje przeglądarek obsługują zapytania
o media w CSS3.
W przeszłości zapewne używałeś JavaScriptu do wykrywania telefonu komórkowego i dodawałeś osobny
arkusz stylów albo przekierowywałeś użytkownika na specjalną witrynę dla urządzeń przenośnych. Mogłeś
też korzystać z J avaScriptu do wykrywania, kiedy rozmiary okna przeglądarki zostały zmienione, i wtedy
modyfikować style, by dostosować układ do okna. Jednak zawsze mieliśmy do dyspozycji podstawy zapytań
o media, ponieważ zawsze mieliśmy możliwość ograniczenia stylów do ekranu lub wydruku:

1 Zapytania o media - przyp. tłum.


86 Rozdział 4 . Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

Tabela 4.1. Obsługa zapytań o media z CSS3 w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 13.0+
Fi refox 4.0+
Internet Explorer 9 . 0+
iOS Safari 4.0+
Opera 11.0+
Safari 5 . 0+

<l i nk rel = " styl esheet ll href= " s creen . cs s " med i a = " screen" />
<l i nk rel = " styl es heet " href= " pri nt . cs s " med i a= " pr i n t " />

Teraz jednak możesz być bardziej wyrafinowany dzięki wykorzystaniu zapytań o media w CSS3 (listing 4 . 1 ) .

Listing 4 . 1 . Prosty przykład zapytań o media

<l i nk rel = " styl es heet " med i a= " screen and
(max-devi ce-wi dth : 480px) " href= " smartphane . cs s " />
<l i nk rel = " styl es heet " med i a= " screen and
(mi n-width: 480px) " href= " screen . cs s " />

Listing 4.1 odpytuj e urządzenie, czy j ego rozdzielczość pozioma to 480 pikseli albo mniej . Jeśli tak j est,
możesz przypuszczać, że jest to smartfon, i ładujesz arkusz stylów smartphone.css. Następnie sprawdzasz,
czy rozdzielczość to przynajmniej 480 pikseli; jeśli tak jest, możesz użyć innego arkusza stylów. Potencjalnie
możesz mieć kilka różnych zapytań o media, jeśli chcesz obsługiwać rozmaite rodzaje urządzeń, rozdzielczości
i orientacji ekranu. Możesz mieć arkusze stylów dla smartfonów, smartfonów z orientacją poziomą, ekranów
androidowych, iPada, iPada z pionową orientacją, przeglądarek z rozdzielczością poziomą mniejszą od 800
pikseli albo przeglądarek z rozdzielczościami szerokoekranowymi. Tak naprawdę możesz być tak dokładny,
jak chcesz.
Będziesz potrzebował dobrego sposobu organizacji wszystkich oddzielnych zapytań. Pamiętaj, że będziesz
także miał wszystkie swoje główne arkusze stylów. Używanie podejścia z listingu 4.1 oznaczałoby dużą liczbę
żądań HTTP w znaczniku head.
Zamiast tego możesz umieścić zapytania o media wewnątrz pliku CSS przy użyciu @med ; a, jak pokazano
na listingu 4.2.

Listing 4 . 2 . Zapytania o media wewnątrz pliku CSS

bady { ba c kgraund : b l ac k ; cal ar: #fff; fan t : narmal 62 . 5%/ 1 . 5 tahama , verdan a , sans-seri f ; }
h l { fant - s i z e : 2em; }
p { fant- s i z e : 1 . 4em ; }
/* style dla smartfonów i bardzo małych rozdzielczości ekranu */
@medi a anly screen and (mi n -wi dt h : 320px) and (max-wi dt h : 400px)
{
bady { ba c kgraund : b l ue ; }
}
/* style dla rozdzielczości ekranu większej od smartfonów. ale mniejszej lub równej J 024px */
@medi a anly screen and (mi n -wi dt h : 401px) and (max-wi dt h : 1024px)
Tworzen ie dostosowującego się projektu za pomocą CSS3 Med ia Queries 87

body { ba c kground : red ; }


}
/* style dla ekranów o bardzo dużej rozdzielczości poziomej */
@medi a only screen and (mi n -wi dt h : 2000px)
{
body { ba c kground : green ; }
}

W kodzie z listingu 4.2 zmienialiśmy kolor tła w zależności od rozdzielczości ekranu. Wszystkie zapytania
o media są częścią tego samego dokumentu CSS, więc ważne jest, by był on odpowiednio zorganizowany
- prawdopodobnie będziesz miał setki wierszy kodu Twojego projektu i dodatkowo CSS z zapytaniami
o media. W zależności od konfiguracji Twojej witryny prostszym sposobem może być edytowanie zapytań
o media, na przykład poprawienie m i n-wi dth : 2000px na mi n-wi dth : 2500px, wewnątrz pliku CSS zamiast
na wszystkich stronach HTML.
Używając składni and, możesz łączyć różne zapytania. Możesz używać kilku właściwości, takich jak wi dth,
hei ght, dev i ce-wi dth, dev i ce-hei ght, o r i entat i on, aspect- rat i o, dev i ce-aspect-rat i o, co1 or, co1 or- i ndex,
monoch rome, reso 1 ut i on, scan i gri d, wraz z rozpoznawanymi rodzajami mediów: a 1 1 , bra i 1 1 e, embos sed,
handhe 1 d, pri nt, project i on, screen i speech. Dzięki połączeniu ich ze sobą będziesz jednocześnie obsługiwać
kilka różnych typów urządzeń i rozmiarów. Warto zauważyć, że max-dev i ce-w i dth j est rozmiarem ekranu
urządzenia (takiego jak iPhone), podczas gdy max-wi dth jest tylko szerokością wyświetlającego obszaru (takiego
jak okno przeglądarki); spójrz na różnice w poniższym kodzie:
/* urządzenia mniejsze od 480px; urządzenia przenośne. iPhone i zwykłe ekrany */
@medi a handhel d and (max-w i dt h : 480px) , screen and (max-dev i ce-w i dt h : 480px) , screen and (max-wi dt h : 480px) {
/* tutaj umieszczasz style */
}
/* układ dla iPada w trybie poziomym */
@medi a onl y screen and (mi n-devi ce-w i d t h : 768px) and (max-devi ce-w i d t h : l024px) and (ori entat i on : l andscape) {
/* tutaj umieszczasz style */
}

Wskazówka
Na kolejnych l istingach użyliśmy k i l ku różnych rodzajów selektorów CSS . Pierwszym z n ich jest selektor
atrybutu użyty w przypadku header ero 1 e=banner] , a d rugim - selektor pseudo klasy, nth-xx O , taki
jak s ect i on : nth -of-type ( 1 ) , wykorzystany w k i l ku przypad kach . Selektory atrybutu nie są nowością
w CSS3, ale w powiązan i u z nowym i ro l a m i ARIA d aj ą więcej możl iwości używan i a selektorów wraz
z CSS-em . Istnieje wiele nowych selektorów CSS3, włącznie z nth-ch i 1 dO i nth-of-type O . Korzystanie
z tych nowych selektorów oznacza, że możesz zrezygnować z używan i a klas przy wielu e lementac h .
Na przykład przy użyciu nth-of-type O możesz wybrać c o drugi wiersz tabeli a l b o element listy. Więcej
inform acj i n a ten temat uzyskasz, czytając http://quirksmode. orgjcssjnthchi/d.htm/.
Wiele s pośród tych nowych selektorów n ie dzi ała w starszych wersj ach I nternet Explorera, więc
polecamy Selectivizr ( http://se/ectivizr. com/) - łatwe w użyciu n arzędzie j avascri ptowe , które
powoduje, że I nternet Explorer rozu mie nowe selektory CSS.

Rysunek 4 . 1 pokazuje wynik wykorzystania przykładowego kodu HTML witryny z wiadomościami


z rozdziału l . Listing 4.3 zawiera skrócony HTML tej witryny. Kod CSS z listingu 4.4 j est przeznaczony
dla domyślnego układu, jaki pokazano na rysunku 4 . 1 .
88 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

Listing 4.3. Skrócony HTML witryny z wiadomościami

<body>

<header rol e= " banner">


<hgroup></hgroup>
</header>

<nav><jnav>

<seet i on> < 1-- ten element sectionjest powtórzony jeszcze trzykrotnie _o>
<art i e l e>
<header></header>
< i mg al t = " t humbnai l " sre = " i mages/box-lOOx lOO . g i f " />
<p></p>
<fart i el e>
</see t i on>

<as i de></as i de>

</body>

Listing 4.4. CSS domyślnego układu ekranu

<styl e>
* {marg i n : O; padd i ng : O ; }
body { ba e kground : #fff; eol or: #000 ; font : normal 62 . 5%/1 . 5 " Pa l at i no Li notype " , " Book Ant i qua" , Pal at i no ,
�ser i f ; marg i n : O aut o ; w i dt h : l260px}
header, nav , see t i on , art i e l e , footer, as i de { d i spl ay: bl oe k ; }
header [rol e=banner] {marg i n : 10px O 20px ; text -al i gn : center ; }
header [rol e=banner] h l { bae kground: url ( i mages/l ogo . g i f) top center no-repeat ; font-s i z e : 5em ;
�paddi ng: 100px O O; text-trans form: upperease ; }
header [rol e=banner] h 2 { font-styl e : i tal i e ; }
header, nav { e l ear: bot h ; w i d t h : 100% ; }
nav { border-bottom: lpx dotted #eee ; paddi ng-bottom : 20px ; text-al i gn : center; }
nav l i { d i spl ay: i n l i ne ; }
nav l i a { font-s i ze : 1 . 4em; padd i ng : . 5em; }
see t i on { fl oat : l eft ; marg i n : O O O 10px ; padd i ng : 10px ; w i d t h : 345px ; }
see t i on h l {marg i n : O O 10px ; }
see t i on art i e l e {marg i n : O O 10px ; }
see t i on art i e l e header p { font-s i ze : lem ; font -we i gh t : bol d ; marg i n : O O lOpx ; }
see t i on art i e l e i mg { fl oa t : l eft ; marg i n : O 5px 5px O ; }
see t i on : nth-of-type ( l ) { e l ear: bot h ; marg i n : O O 10px; mi n-hei ght : 200px; padd i ng : 1% 1% 1% 30% ; pos i t i on :
�rel at i ve ; w i d t h : 69%; }
see t i on : nth-of-type ( l ) art i el e {marg i n : O ; }
see t i on : nth-of-type ( l ) art i el e i mg { fl oa t : none; h e i g h t : 200px ; l eft : O ; pos i t i on : absol ute ; top : 10px ;
�wi dt h : 360px}
see t i on : nth-of-type (2) {margi n-l eft : O ; }
as i de {fl oat : r i g ht ; marg i n - l eft : 10px ; w i d t h : l30px ; }
as i de img { border: lpx sol i d #ee e ; di spl ay: bl o e k ; marg i n : O auto 10px ; }
footer { e l ear: bot h ; }
h l { font - s i z e : 2em; }
p { font- s i z e : 1 . 4em ; }
</styl e>
Tworzen ie dostosowującego się projektu za pomocą CSS3 Med ia Queries 89

MNÓSTWO WIADOMOŚCI

Najc.hętniej aylany d,�isiejny a.rłykul


Tytul.lrt"kl1ll1
1OIl:obłof'doll31O
rtIJttlIHqUC N.b:IU:."'1.1 morbi trisdqut łtt\4'CtUł ClI nt'Iuł.t nut.tsuul:l f� 2':- tutpIJ -Sł'JI1$.. \"łStibulwn ttIItOJ' qu.ll� (łUgI"l "I.\t, WUlOfS
eget.. tm,porslt .l1I'W1. il.'\�"". Donec eu libero sit � quam�Htas iem»l!f. AmNnultriciH mi \-:it.al'est. �tłuris pl.Kti"ol tel<ftlMd lec.
OwsqueSlt � �1 et Y.p:!en. uIhmrorper phMeua_ \'es�umet',1It h'l5l. condlmentum sed. commodo \'ltae, ott\Ml!' Slt .t:mi'l,. \\'l5.1. Aene:lń
ft'l'młlltwn. •\.11 łgł1 lmQdunl 0XId1-'T\fnłum,. łfO$lptU."l\ I'UII'U!T1 0rQ. S.J.9ttif Itmpul La(uł tnim � d1l,l. 00n0K- non �m "\ L. tutpil pul\V\.lT
fKilisi5. \:tft!lis. Pr.w:sentdapibus. � id rur5W ra�cibus. tortorneqI.W ��.u augue. t'U \'\Uput.1tł! IDłg:N eros eu.

Sport Rozrywka Komputery


wytworna
Mul 1
reklama
flłLltn1ł':SqUł h.I't1itartl lTLQWJ triłlltf\lł
S@OKtUsetnetus@[ malesuoMSa Umes
oK [utpl$�esw. Pelłfluesąuf'
Nbitam mo:rbl tristique smectus et
ttetu!l fłm.t.lemtd.lil�I!5 � � wytworna
SPOrt _ Muł i R91r;xwk._ Mył 2 reklama
'lU""p.o40;:!:l:& '1!boopod.ł'!OIO
rClUtnlłłqUł ł\.amloml morbl !nłIlqut � te rtlttu$ f'�ltsqut !ulbI!.m! morbi tn$.lJquł łtr*tUS łł �fuł r l I f;r".$bqut łrtnł(ruS ł1 nCltu.t
E'tlltnt<ttquł h.1Ibit.lnt mot
et ma!esuada lame ac Iwpis �e5Us. et malesuada fołmł!S K turpi!l esesus. et ma.lesu�.a t.unes .lC tutpi5 esestas..

Soort - tylu. J Rouywk.a - Muł 3 Komputffl - t)'lul 3


* 1IittDp.o4.0 :lll:t itiołapo4.&:lm6 i�:ma
PlI!ł1i!'2\�II! h.łbil.l1'l1 morbl U'l:Stzqui!' � fł l\l!(ul Pł!u.r.t�ł! habJU:!'It O\Otbt t1Ubque�t � MI'uJ Pl!-lI.entHquf'NbiW'lt t'AOlbt tnmqut' � ., �[u.!I
et INlemadil f.wws ił .. ł\.I.rpii eg;nl.,:;. et małmla<liJ f� ;1II;: turpii eg�;\S. e� maJl!$l.Iad� t.t."MS iK twpis 'Sm....

Rysunek 4.1. Witryna z wiadomościami z domyślnym CSS-em

Rysunek 4.2 pokazuje układ na mniejszym ekranie z wykorzystaniem zapytań o media z listingu 4.5.

Listing 4 . 5 . CSS przeznaczony dla mniejszych ekranów

/* style dla rozdzielczości ekranu większych od smartfonów. ale mniejszych lub równych 1280px */
@medi a only sereen and (mi n -wi dt h : 48 1px) and (max-wi dt h : 1259px)
{
body { w i d t h : 800px ; }
see t i on {marg i n : O O O lOpx ; }
see t i on : nth-of-type ( l ) , see t i on { e l ear: non e ; fl oat : l eft ; padd i ng : lOpx; w i dt h : 375px ; }
see t i on : nth-of-type ( l ) art i el e i mg {fl oat : l eft ; he i ght : auto ; pos i t i o n : rel at i ve ; w i d t h : auto ; }
see t i on : nth-of-type (2) {margi n-l eft : lOpx ; }
see t i on : nth-of-type (3) { e l ear: bot h ; marg i n - l eft : O ; }
as i de { e l ear: bot h ; fl oat : l eft ; w i d t h : lOO% ; }
as i de img { fl oat : l eft ; marg i n : O lOpx O O ; }

Wreszcie, listing 4.6 zawiera zapytania o media w CSS-ie przeznaczone dla smartfonów albo znacznie
zmniejszonego okna przeglądarki (rysunek 4.3). Zasadniczo tych kilka dodatkowych wierszy CSS-a sprawia,
że w porównaniu z ekranem o mniejszych rozmiarach niektóre elementy są ukrywane lub zmniejszane.
90 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

MNÓSTWO WIADOMOŚCI

Sport

:o.�ptKer.�dhlfl'dlolo.�lil_tilftur-en
uJ�płUtftI•. \'� ...I1 ww. �
�"'łIM�mort-.. lnJtICiIII' _ �IMt1łtt\
I:tHWlDduNOlIIda-.um. _1ptWn
Nd. -,,* '"'IIA. II'IJw."''''''' '''''' MnIi
..
m.&lHwd.I l_ M NlJ'd epIUL

MNtI'Iora.�"wm;wLAaol_łoCoiIui.o.-cMn
rH1lWftlD'l. ....

flWIIo d\fWlld pal\ __ Ud.J.Wt.. L�lIIIlIs.. FriltHfll �


�Ił\
'*łUł",(\I""' �_tltqUłtplUl�W
,-­

'''IIlpuuIlt m.lf1'l_w. � f_.I(łwJU �


� 1\.INa'II ���,"_,"

KOlllpuh!l')'
"O"'ł'II!"'J,..;:txI\llj
.---

P't!itmttqut IlltuMllIflDfbiU\Jlłąlll"� .. ftllUlft


� �.ł(", r;'d�
I'QImttqtM �mott.. lJtRlqIII'ltfIfaW .,fltW:SH
�,""""" ł(�"-""
MOlrvwu h1!!1 J

r���1I1JIIMpI'NnfCDIII "nrlWfl � Ntwnl.roomlnJtlCilll' � "lIłtIitft


�lAIrIH .I(łlaf'd.g,Hr.U. IIWIowoS.o_ JoC IWJId ....-.

wytworna wytworna
reklama reklama

Rysunek 4 . 2 . Witryna z wiadomościami na mniejszym ekranie

Listing 4.6. CSS przeznaczony dla smartfonów

/* style dla smartfonów i bardzo małych rozdzielczości ekranu */


@medi a onl y screen and (max-w i dt h : 480px) , onl y screen and (max-devi ce-wi d t h : 480px) {
body { w i d t h : lOO% ; }
header [rol e=banner] h l { bac kground- image : url ( i mages/l ogo-smal l . g i f) ; font - s i z e : 3em; padd i ng : 50px O O ; }
sec t i on : nth-of-type ( l ) , sec t i on {marg i n : O O lOpx ; m i n - h e i g h t : i nheri t ; padd i ng : O 1%; w i d t h : 98% ; }
header [rol e=banner] h2 , i mg , sec t i on art i c l e p , asi de { d i spl ay : none ; }
sec t i on h2 a { border-bottom: lpx dotted #999 ; d i s p l ay: bl o c k ; text-decora t i o n : none ; }
nav , sec t i on art i cl e : l ast -of-type h2 a { border: none ; }

Kied y używać zapytań o med ia


Choć możesz tworzyć oddzielne style dla różnych urządzeń i rozmiarów ekranu, powstaje pytanie, czy zawsze
powinieneś korzystać z zapytań o media. Bez wątpienia mogą być one użyteczne na komputerach stacjonarnych
i laptopach, ale rynek urządzeń przenośnych od pewnego czasu się zmienia. Bardziej nowoczesne smartfony,
takie jak iPhone i urządzenia z systemem Android, posiadają przeglądarki, które są prawie identyczne j ak
przeglądarki desktopowe i z większością rzeczy sobie radzą, a interaktywność urządzeń pozwala użytkownikowi
stosunkowo łatwo wybierać i powiększać fragmenty witryn.
Tworzen ie dostosowującego się projektu za pomocą CSS3 Med ia Queries 91

MNÓSTWO WIADOMOŚCI

Najchętniej czytany dzisiejszy artykuł


Tytuł artykułu
Sport
Sport - tytuł 1
Sport - tytuł 2
Sport - tytuł 3

Rozrywka
Rozrywka - tyłuł 1
Rozrywka - tytuł 2
Rozrywka - tytuł 3

Komputery
Komputery - tytuł 1
Komputery - tytuł 2
Komputery - tytuł 3
Rysunek 4.3. Witryna z wiadomościami na smartfonie

Korzystanie z zapytań o media oznacza na ogół pokazywanie lub ukrywanie treści w zależności od
rozmiaru ekranu. Wiele witryn, takich jak http://youtube.com/, http://facebook.com/, http://cnn.com/ oraz
http://nfl.com/, wykrywa, czy użytkownik używa urządzenia przenośnego, i przekierowuj e go na wersj e
przeznaczone dla takich urządzeń. Witryny te zawieraj ą dużo treści z mnóstwem danych, zdjęć, filmów
wideo, reklam, Flasha i innych rzeczy - gdyby korzystać tylko z zapytań o media, smartfon nadal musiałby
pobierać wszystkie te dane, mimo że użytkownik nie mógłby ich zobaczyć. A zatem to, czy potrzebuj esz
jedynie nowych stylów, czy zupełnie oddzielnej witryny dla urządzeń przenośnych, zależy od treści witryny,
ale jeśli masz zmienić j edynie układ i dodać kilka szczegółów, wtedy powinieneś prawdopodobnie użyć
zapytań o media. Kilka znakomitych sposobów ich użycia znajdziesz na http://mediaqueri.es/.

O bsługa iPh one' a i u rząd zeń z systemem And roid


Jeśli używałeś zapytań o media do obsługi iPhone'a albo urządzeń z systemem Android, być może zauważyłeś,
że nie uwzględniaj ą one zmian CSS-a dotyczących małych ekranów. Jest tak dlatego, że nowoczesne
przeglądarki na smartfonach mają duże możliwości i domyślnie wyświetlają stronę internetową tak samo
jak przeglądarki desktopowe. Następnie przeglądarka zmniejsza witrynę, tak aby mieściła się ona na małym
ekranie, co może często skutkować wyświetlaniem drobnego tekstu i obrazków, przez co użytkownik musi
powiększać fragmenty witryny, by ją czytać i przeglądać. Możesz zmusić przeglądarkę do używania szerokości
urządzenia jako szerokości obszaru, w którym przeglądarka pokazuje obraz, znanego również jako viewport.
W tagu head należy umieścić następujący kod:
<meta name= " v i ewport" content = " w i dth=dev i ce-w i dt h ; i n i t i al -scal e= 1 . 0 ; " j>
92 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

PRZEPIS DLA POCZĄTKUJĄCYCH


Korzystanie z własnych czcionek przy użyciu @font-face
Chociaż deklaracja @font-face nie pojawiła się dopiero w CSS3, została w nim uatrakcyjniona. Projektanci
i twórcy stron internetowych mają teraz do dyspozycji wiele możliwości wyboru i sposobów implementacji
czcionek w swojej witrynie. Tabela 4.2 pokazuje, które wersje każdej z przeglądarek obsługują @font-face.

Tabela 4 . 2 . Obsługa @font-face w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 13.0+
Fi refox 4.0+
Internet Explorer 6 . 0+
iOS Safari 4.0+
Opera 11.0+
Safari 5 . 0+

I
Uwaga
Deklaracja @font- face została włączon a do s pecyfikacj i CSS2 w 1998 roku , j e d n a k była w zasadzie
n ieużywan a z powodu słabej im plementacj i w przeglądarkach, zamieszania z rodzaj a m i p l i ków czcionek
i obaw dotyczących problemów prawnych i licencyjnych związanych z wykorzystywan iem czcionek. Internet
Explorer w wersj i 4 obsługiwał nawet dodatkowe , zewnętrzne czcion ki, choć tyl ko w formacie EOT.

Używając deklaracji @font-face, możesz osadzić na stronie swoje własne czcionki za pomocą j edynie
kilku wierszy CSS-a. Na rysunku 4.4 pokazaliśmy czcionkę Anagram (dalej podamy informacje o tym, skąd
zdobyć czcionki) w elemencie h l, chociaż może być ona użyta w dowolnym elemencie.

, ,

JANOSTWO WI�DOA\OSCI
Rysunek 4.4. Własna czcionka wyświetlona przy użyciu @font-face

Listing 4.7 pokazuje kod potrzebny do uzyskania efektu z rysunku 4.4 i prezentuje przykład najprostszego
użycia @ font-face. Podajemy nazwę czcionki Anagram jako wartość właściwości font- fami l y (można
nazwać ją dowolnie), a później będziemy się ponownie do niej odwoływać; spójrz na wartości czcionek h l .
W deklaracji @font-face właściwość s rc wykorzystuje ten sam katalog, w którym znajduje się strona HTML;
jeśli chcesz, możesz umieścić czcionkę w innym katalogu.

Listing 4 . 7 . Deklaracja @font-face w swojej najprostszej postaci

<styl e>
@ font - face {
font- fami l y : Anagram;
Korzystanie z własnych czcionek przy użyciu @font-face 93

src : url ( ' anagram . ttf ' ) ;


}
hl {
font-fam i l y : Anagram, Tahoma, Verdana , sans -ser i f ;
font-s i z e : gem;

<jstyl e>
<header rol e= " banner">
<hgroup>
<hl>Mnóstwo wi adomośc i <j h l>
<h2>Dostarczamy Ci najróżn i ej szych wi adomoś c i ! <jh2>
<jhgroup>
<jheader>

Formaty plików i d ziałanie czcionek w róż nych przegląd arkach


Na listingu 4.7 użyliśmy tylko czcionek typu .ttf (TrueType/OpenType) . Jak można się spodziewać, między
przeglądarkami występują różnice implementacyjne. Istnieje wiele różnych formatów plików czcionek:
• Embedded OpenType (.eot),
• OpenType PS (.ot[),
• TrueType/OpenType (.tt[),
• SVG (.svg),
• WOFF (.wojf).

WOFF j est nowym sieciowym standardem krojów pisma. Internet Explorer w wersjach 8 i starszych
wymaga czcionki .eot, ale Internet Explorer 9 obsługuje nowy format WOFF. A chociaż Safari obsługuje .ttf
już od jakiegoś czasu, dopiero niedawno iPhone i iPad zaczęły wspierać ten format; wcześniej potrzebowały
czcionki .svg. Powinieneś więc dostarczać tę samą czcionkę w różnych formatach. W tym celu musisz albo
samodzielnie przekonwertować czcionki, albo użyć narzędzia, takiego jak Font Squirrel z http://wwwjontsquirrel.
com/o Narzędzie @font-face Generator pozwala Ci wgrać czcionki, a następnie przekształcić je na inny format.
Generuje ono również odpowiedni CSS, który umożliwia działanie czcionki w różnych przeglądarkach,
co pokazano na listingu 4.8.

Listing 4.8. Działający w różnych przeglądarkach @font-face

@font-face {
font -fami l y: ' AnagramRegul ar ' ;
src : url ( ' typejanagram-webfont . eot ' ) ;
src : url ( ' typejanagram-webfont . eot ?#i efi x ' ) format ( ' embedded-opentype ' ) ,
url ( ' typejanagram-webfont . woff ' ) format ( ' woff ' ) ,
url ( ' typejanagram-webfont . ttf ' ) format ( ' truetype ' ) ,
url ( ' typejanagram-webfont . svg#AnagramRegul ar ' ) format ( ' svg ' ) ;
font -we i gh t : normal ;
font-styl e : normaI ;

Na listingu 4.8 odwołujemy się do czterech różnych formatów tej samej czcionki. Warto być może
umieścić swoj e czcionki w oddzielnych katalogach, tak j ak robisz to z obrazkami. Kolejność czcionek
94 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

w powyższym kodzie jest związana z problemem dotyczącym działania Internet Explorera. Było już wiele
wersji powyższego kodu, ale w miarę rozwoju przeglądarek zniknęło kilka problemów. Powyższy kod jest
obecnie2 dostarczany przez Font Squirrel, gdy generujesz pakiet z czcionkami, lecz oczywiście w przyszłości
może się on zmienić.

U sługi związane z krojami pisma


Znalezienie odpowiedniego kroju pisma może być trudne, a znalezienie takiego, który może być użyty
w sieci zgodnie z prawem, może być jeszcze trudniejsze. Font Squirrel pozwala Ci przekształcić czcionki
na różne potrzebne formaty. By to zrobić, musisz zaznaczyć przycisk wyboru, przy którym jest napisane
"Yes, the fonts I am uploading are legally eligible for web embedding"3. Jeśli go zaznaczysz, a nie jesteś
w pełni przekonany, że tak j est, możesz mieć problemy prawne.
Jeśli potrzebujesz darmowej czcionki, sugerujemy przejrzenie biblioteki krojów na witrynie Font Squirrel,
ponieważ ta witryna oferuj e nie tylko generator czcionek - jej głównym celem jest udostępnianie czcionek,
które są darmowe i które można wykorzystywać w zastosowaniach komercyjnych. Ponadto witryna nie
zawiera po prostu j akichkolwiek darmowych czcionek, ale zawiera czcionki wysokiej j akości. Font Squirrel
nie jest jedynym miejscem, w którym można pobrać darmowe czcionki do użytku w sieci, ale gdy będziesz
szukał ich gdzie indziej, bądź świadomy aspektów prawnych.
Innym rozwiązaniem dotyczącym darmowych czcionek jest Google Font Directory (http://www.google.
com/webfonts). Za pomocą dostępnego API bardzo łatwo skorzystać z oferowanych tam czcionek. Ponieważ
czcionki znaj dują się na serwerach Google, czasy ładowania są minimalne. Musisz tylko znaleźć czcionkę
i wybrać "Quick-use"4. Narzędzie przygotowuje HTML i CSS, których potrzebujesz, a dodatkowo oferuj e
inne grubości czcionki, j eśli dana czcionka je posiada. Dostarczany przez to narzędzie kod HTML, który
należy umieścić w znaczniku head, jest podobny do poniższego:
<l i n k href= ' http : //fonts . googl eap i s . com/css?fam i l y=Yanone+Kaffeesatz ' rel = ' styl esheet ' type= ' textjcss ' >

Google daje Ci również nazwę czcionki, którą możesz umieścić w swoim kodzie CSS:
hl { font -fami l y : ' Yanone Kaffeesatz ' , ari al , seri f; }

Tak, to naprawdę proste.

Wskazówka
W poprzed n ich przykładach mogłeś zauważyć, że po deklaracj i dodatkowej czcionki podajemy l istę
n i e których " bezpiecznych " do stosowan i a w sieci czcionek, jak na przykład rodzinę czcionek ' Yanone
Kaffeesatz ' , a r i a l , ser i f. Rezerwową czcionkę podajemy, na wypadek gdyby cokolwiek poszło nie
tak podczas pobiera n i a dod atkowej czcion ki . Może się poj awić problem z serwerem i przeglądarka
może być n iezd o l n a do pozyskan ia dodatkowej czcionki, którą chcemy wykorzystać, a poprzez
zam ieszczenie listy czcionek dajemy m ożl iwość awaryj nego użycia czcion ki, o której wiemy, że jest
dostępna. Listy czcionek powinny być dodawane zawsze wtedy, kiedy używa się CSS-owej właściwości
font- fami l y .

2 W kwietniu 2012 roku - przyp. tłum.


3 Tak, czcionki, które wgrywam, mogą być zgodnie z prawem używane na witrynach internetowych - przyp. tłum.
4 Szybki wybór - przyp. tłum.
Tworzenie przycisków za pomocą gradientów CSS i wielu tel 95

Jeśli Font Squirrel albo Google Font Directory nie mogą udostępnić czcionki, której szukasz, wypróbuj
inne serwisy. Ponieważ licencjonowanie stanowi problem już od lat, wielu spośród głównych producentów
czcionek zaczęło albo tworzyć swoje własne struktury pozwalające Ci zgodnie z prawem i bezpiecznie używać
ich czcionek, albo współpracować z inną organizacją, która pomaga dostarczać czcionki.
Serwisy takie jak FontDeck i TypeKit umożliwiają Ci zarejestrowanie się i dokonanie wyboru spośród
szeregu czcionek, z których wszystkie zostały poprawione w celu uzyskania lepszej czytelności na ekranie.
FontDeck i TypeKit dostarczaj ą czcionki na odrobinę różne sposoby, ale konfiguracja, wybór i aktywacja
czcionek w obu serwisach nie powinny nikomu sprawiać problemów. Oba serwisy mają darmowe konta
z pewnymi ograniczeniami i oba oferują różne pakiety cenowe.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Tworzenie przycisków za pomocą gradientów CSS
i wielu teł
Gradientów CSS3 możesz używać do tworzenia miłych i subtelnych albo jakichś zwariowanych, pokracznych
gradientów. Nie musisz tworzyć obrazków, a gradienty są skalowalne, więc kiedy j uż się z nimi oswoisz,
sprawiaj ą dużo mniej kłopotu w porównaniu z tworzeniem, edycj ą i ponownym tworzeniem obrazków,
ponieważ możesz zrobić wszystko za pomocą kodu. Tabela 4.3 pokazuje, które wersje każdej z przeglądarek
obsługują gradienty CSS3.

Tabela 4.3. Obsługa gradientów w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 13.0+
Fi refox 4.0+
Internet Explorer
iOS Safari 4.0+
Opera 11.0+
Safari 5 . 0+

Listing 4.9 zawiera kod prostego gradientu od białego do czarnego koloru, który pokazano na rysunku 4.5.

Listing 4.9. Prosty liniowy gradient CSS

div {
hei ght : 200px ;
w i d t h : 200px;
bac kground: ur l (grad i ent . gi f) ; /* dla przeglądarek. które nie potrafią tworzyć gradientów */
bac kground: -moz - l i near-gradi ent (wh i t e , bl a c k) ;
bac kground: -web ki t - l i near-gradi ent (wh i t e , bl a c k) ;
bac kground: - l i near-grad i ent (wh i t e , bl ack) ;
}
96 Rozdział 4 . Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

jakiś tekst

Rysunek 4.5. Prosty liniowy gradient CSS

Najpierw zostaje określony rodzaj gradientu (liniowy lub radialny), po czym następują nawiasy, w których
zapisany jest początkowy i końcowy kolor gradientu. Zauważ, że kod ma cztery różne deklaracj e. Pierwsza
zawiera gradient w postaci pliku graficznego, na wypadek gdyby przeglądarka nie obsługiwała gradientów
CSS3; -moz-l ; nea r-grad; ent jest przeznaczona dla przeglądarek Mozilla (Firefox); -web k i t- l ; nea r-grad; ent
dla przeglądarek opartych na WebKit (Safari i Chrome); ostatnia deklaracja tła jest oficjalną składnią gradientu
CSS3, ale obecnie żadna przeglądarka jej nie obsługuje.

Uwaga
W 2008 roku Web Kit był pierwszym s i l n i kiem , który zaczął używać gradientów, lecz m iał swoj ą własną
składnię. W m iarę jak grad ienty CSS stawały s ię bardziej popularne, Mozi lla zaim plementowała składn ię
znacznie bl iższą tej z oficjalnej s pecyfikacj i . Jednak w styczniu 2011 roku ogłoszono, że Web Kit zmieni
s kładnię gradientów i zaczn ie używać tego samego stylu co Moz i l l a i oficj a l n a s pecyfi kacj a , co jest
wspaniałą wiadomością dla każdego, pon ieważ oryginalna składn ia WebKit była bardziej skomplikowana
i n ie była oparta na standardach . W tej książce używamy nowej s kładn i WebKit.

Na listingu 4 . 1 0 używamy przycisków i kombinacji selektorów atrybutów, właściwości box- s hadow


i border- rad ; us, wielu teł oraz gradientów CSS do uzyskania efektów z rysunku 4.6. We właściwości
background oddzielamy poszczególne style tła przy użyciu przecinka. Wobec tego najpierw umieszczamy
obrazek, z którego chcemy skorzystać, a potem ustawiamy style gradientu. Jeśli style gradientu byłyby użyte
wcześniej, przykryłyby obrazek.

Listing 4.10. Efekty gradientu na przyciskach

i nput {
border: none;
-webki t-box-s hadow: O 1px 5px rgba ( O , O , O , . 4 ) ;
box-s hadow: O 1px 5px rgba ( O , O, O, . 4 ) ;
-webki t-border-rad i us : 10px ;
border-radi us : 10px ;
cursor: poi nter;
col or: #fff;
fon t : bol d 1 . 2em Ari al , Hel vet i c a , sans-seri f;
marg i n : O 10px O O ;
padd i ng : 10px 10px 10px 30px;
text-shadow : O 2px 2px rgba ( O , O, O, 0 . 25 ) ;
}
i nput [type= "subm i t " ] {
bac kground: url ( i magesjaccept . png) 8px 55% no-repeat #91B009 ;
bac kground: url ( i magesjaccept . png) 8px 55% no-repeat , -webk i t - l i near-gradi ent (#91B009, #578730) ;
bac kground: url ( i magesjaccept . png) 8px 55% no-repeat , -moz - l i near-grad i ent (#9 1 B009 , #578730) ;
Tworzenie przycisków za pomocą gradientów CSS i wielu tel 97

bac kground: url ( i magesjaccept . png) 8px 55% no-repeat , -l i near-gradi ent (#91BD09 , #578730) ;
}

i nput [va l ue=" Anul uj "]


bac kground: url ( i magesjcros s . png) 8px 55% no-repeat #b5 3 1 0 9 ;
bac kground: u r l ( i magesjcross . png) 8px 55% no-repeat , -web k i t - l i near-gradi ent (#b53 109 , #540303) ;
bac kground: url ( i magesjcross . png) 8px 55% no-repeat , -moz - l i near-gradi ent (#b53 109 , #540303 ) ;
bac kground: url ( i magesjcross . png) 8px 55% no-repeat , - l i near-grad i ent (#b5 3 1 0 9 , #540303 ) ;
}

i nput [type= " reset "]


bac kground: url ( i magesjerror. png) 8px 55% no-repeat #fObb 18 ;
bac kground: url ( i magesjerror. png) 8px 55% no-repeat , -webki t - l i near-grad i ent (#fObb18 , #a46b07) ;
bac kground: url ( i magesjerror. png) 8px 55% no-repeat , -moz - l i near-grad i ent (#fObb18 , #a46b07) ;
bac kground: url ( i magesjerror. png) 8px 55% no-repeat , - l i near-grad i en t ( #fObb 18 , #a46b07 ) ;
}

Rysunek 4.6. Gradienty CSS3 na przyciskach

Możesz uzyskać znacznie więcej niż tylko zwykłe przenikanie dwóch kolorów. Używaj ąc zatrzymania,
możesz dodać wiele kolorów do gradientu, co pokazano na rysunku 4.7. Zatrzymania są dodawane po każdym
przecinku i mogą zawierać pozycję, w której się zaczynają, jak pokazano na listingu 4.1 1 .

Listing 4.11. Gradienty z wieloma zatrzymaniami

div {
hei ght : 200px ;
w i d t h : 200px;
bac kground-col or : #00 0 ;
bac kground: -moz - l i near-gradi ent ( l eft , #000000 0 % , # FFFFFF 2 5 % , #000000 50% , # FFFFFF 7 5 % , #000000 100%) ;
bac kground: -web k i t - l i near-grad i ent (l eft , #000000 0% , # FFFFFF 25%, #000000 50% , # FFFFFF 75%, #000000 100%) ;
bac kground: - l i near-grad i ent ( l eft , #000000 0%, #FFFFFF 25%, #000000 50% , #FFFFFF 75%, #000000 100%) ;
}

Rysunek 4.7. Gradienty CSS3 z wieloma zatrzymaniami

Listing 4. 1 1 pokazuje kod użyty do utworzenia lewego obrazka z rysunku 4.7. Gradient staje się bardziej
skomplikowany, ale zasadniczo każdy przecinek rozpoczyna nowy fragment informacji o gradiencie, w którym
przekazuj esz wartość koloru, a następnie procent oznaczający miejsce, w którym ten kolor się zaczyna.
98 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

Ponadto możesz obrócić gradient przez zmianę deklaracji l eft, na przykład na 45deg albo l eft top,
jak pokazano na listingu 4.12, który tworzy prawy obrazek z rysunku 4.7.

Listing 4.12. Obrócone gradienty z wieloma zatrzymaniami

div {
hei ght : 200px ;
w i d t h : 200px;
bac kground-col or : #00 0 ;
bac kground: -moz - l i near-gradi ent (45de g , #000000 0 % , #FFFFFF 2 5 % , #000000 50% , # FFFFFF 7 5 % , #000000 100%) ;
bac kground: -web k i t - l i near-gradi ent (45de g , #000000 0%, #FFFFFF 25%, #000000 50% , #FFFFFF 75%, #000000 100%) ;
bac kground: - l i near-grad i ent (45deg, #000000 0%, # FFFFFF 25%, #000000 50%, # FFFFFF 75%, #000000 100%) ;
}

Istnieje mnóstwo opcji związanych z gradientami CSS3, włączając w to tworzenie gradientów radialnych.
Zalecamy wypróbowanie niektórych spośród generatorów gradientów, by nabrać wprawy:
• http://colorzilla.com/gradient-editor/,
• http://display-inlinejr/projects/css-gradient/.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


U piększanie witryny za pomocą transformacji i przejść
Dawniej twórcy stron internetowych musieli używać J avaScriptu albo wtyczek, takich jak Flash, żeby uzyskać
na stronie internetowej efekty i animacje. Teraz j ednak możesz robić takie rzeczy jak obracanie i zmiana
rozmiaru elementów HTML przy użyciu jedynie CSS-a. Ponieważ żadna przeglądarka nie przyjęła jeszcze
oficjalnej składni CSS-owej, musisz dodawać CSS dla każdego producenta. Oznacza to, że będziesz mieć
zduplikowany CSS, ale na razie j est to konieczne, a nie wiąże się tak naprawdę z dużą ilością dodatkowej
pracy. Tabela 4.4 pokazuje, które wersj e każdej z przeglądarek obsługują transformacj e i przejścia.

Tabe la 4.4. Obsługa transformacji i przejść w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 13.0+
Fi refox 4.0+
Internet Explorer 9 . 0+
iOS Safari 4.0+
Opera 11.0+
Safari 5 . 0+

Rysunek 4.8 pokazuje transformację obracającą zastosowaną do elementu fi gure.


Aby utworzyć efekt z rysunku 4.8, zastosuj właściwość trans form z wartością rota te do elementów
fi gure i h l . Jak pokazano na listingu 4.13, musisz użyć CSS-owych prefIksów kilku producentów, aby efekty
działały w Web Kit, Mozilli, Operze i Internet Explorerze.
Upiększanie witryny za pomocą transformacj i i przejść 99

Pe llentesque habilant morbi tristique senectus et netus. Vestibu lum tortor quam , feug iat vitae,
>­ ultricies eget. tempor sit amet. ante. Do nec eu libero sit amet quam egestas semper. Aenean
C ultricies mi vitae est.

O
... Pe llentesque habitant morbi tristique
-
en
se nectus et netus et malesuada fames ac
turpis egestas Vestibu lum tortor q uam ,
-
::l
feugiat vitae, ultricies eget, tempo r sit
amet. ante. Donec eu li bero sit amet

>.
-
quam egestas sem pe r. Aenean ultricies
mi vitae es! M auris placerat eleifend leo.
- erat volutpa! Nam dui mi. tincidunt quis.
en accumsan po rttitor, facilisis luctus, metus
CI)
0-
Pe llentesque habitant morbi tristique
O senectus et netus et malesuad a fames ac
ł- turpis egestas Vestibu lum tortor q uam ,
WidOk z
feugiat vitae, ultricies eget, tempor sit na szegO
POkoju h otelO w
ego
amet. ante. Donec eu li bero sit amet
quam eg estas sem pe r. Aenean ultricies
mi vitae es! M auris placerat eleifend leo.
Q uisque sit amet est et sapien
ullamcorpe r pharetra. Vesti bulum erat
wisi, condi mentum sed, commodo vitae, ornare sit amet, w isi. Aenean ferme ntu m , eliteget
ti ncidu nt cond imentum, eros ipsum rutrum orci, sag ittis tempus lacus en im ac dui. Donec non
en im in tu rpis pulvinar facilisis. Ut felis.

Rysunek 4.8. Obrazek i tekst obrócone za pomocą CSS-a

Listing 4.13. Transformacje CSS

* {
marg i n : O ;
paddi n g : O ;
}
body {
fon t : normal . gem Ari al , Hel vet i c a , sans -ser i f ;
pos i t i on : rel ati ve;
padd i ng : 30px 10px 10px 75px;
width: 600px ;
}
hl {
l eft : - IOOpx ;
pos i t i on : absol ute;
top : 160px;
text-al i gn : l eft ;
-webki t-transform: rotate (270deg) ;
-moz-transform: rotate (270deg) ;
-o-trans form: rotate(270deg) ;
-ms-transform: rotate(270deg) ;
trans form : rotate (270deg) ;

fi gure {
bac kground: #fff;
border: lpx sol i d #BFBFB F ;
-webki t-box-s hadow: 2 p x 2px 4px rgba ( O , O , O , 0 . 3) ;
-moz-box-shadow: 2px 2px 4px rgba (O , O , O , 0 . 3) ;
box-s hadow: 2px 2px 4px rgba ( O , O , O , 0 . 3 ) ;
d i spl ay: bl oc k ;
fl oat : ri ght ;
marg i n : 20px 20px 50px 50px ;
paddi ng: 5px;
text-al i gn : center;
-webki t-transform: rotate (lOdeg) ;
100 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

-moz-transform: rotate (lOdeg) ;


-o-trans form: rotate(lOdeg) ;
-ms-transform: rotate (lOdeg) ;
trans form : rotate (lOdeg) ;
}
fi geapt i on {
e l ear: bot h ;
d i spl ay: bl oe k ;
font-wei ght : bol d ;
padd i ng : . 5 em O ;
}
P {
marg i n : O O lem;
}

<hl>To j est tytuf strony<jhl>


<p>Pel l entesque hab i t ant morbi t r i s t i que sen ......<jp>
<fi gure>
< img a l t = " p i aszezysta pl aia" sre = " beae h . j pg " j>
<fi geapt i on>
W i dok z naszego pokoj u hotel owego
<jfi geapt i on>
<jfi gure>
<p>Pel l entesque hab i t ant morbi t r i s t i que sen ...... <jp>

Na rysunku 4.8 widać efekt użycia funkcji rotate CSS-owej właściwości t ran s form, lecz istnieją jeszcze
inne, których możesz używać: trans l ate, scal e i s kew. Listing 4.14 pokazuje przykład kodu potrzebnego do
implementacji tych efektów (nie zapomnij dodać prefiksów specyficznych dla przeglądarek) . Kod pokazuj e
efekty transformacji zastosowanych na tekście, ale mogą być one wykorzystane na dowolnym elemencie,
włączając w to body.

Listing 4. 14. Więcej możliwości transformacji CSS

h l { t ransform: transl ate ( IOOpx , 200px) }


hl { t ransform: s kew(40deg) ; }
h l { t ransform: seal e ( 2 ) ; }
/* Możesz również połączyć je razem w pojedynczej deklaracji */
h l { transform: trans l at e ( IOOpx , 200px) s kew(40deg) seal e (2 ) rotate (40deg) ; }

Możesz również stosować przejścia i animacje. Istnieje różnica między przejściami i animacjami w CSS-ie.
Przejście jest procesem zmiany między dwoma stanami, wywołanym przesunięciem myszą kursora nad obiekt
( : hover) albo kliknięciem przyciskiem myszy ( : act ; ve). Animacje CSS działają natomiast samodzielnie.
Przejścia były dostępne od dawna w Web Kit, a Mozilla i Opera już je implementuj ą; niestety, obecnie
nie robi tego IE. Przejścia wykorzystują pokazaną wcześniej właściwość trans form i mogą być zastosowane
w przypadku pseudoklas : hover albo : act ; ve. Zamiast natychmiastowego zmieniania wartości elementu,
gdy przesuwasz nad nim kursor myszą, możesz utworzyć animację przechodzącą do tych nowych wartości
przy użyciu właściwości CSS tran s ; t ; on. Rysunek 4.9 pokazuje prosty przykład zwykłego tekstowego łącza.

To jlest ·heze
Rysunek 4.9. Łącze tekstowe z efektem hover
Upiększanie witryny za pomocą transformacj i i przejść 101

Rysunek 4.10 pokazuje efekt w trakcie przechodzenia tła łącza do koloru czarnego i tekstu do koloru
białego oraz ostateczny efekt przesunięcia kursora nad łącze. Trudno jest oddać ten efekt w drukowanej,
czarno-białej książce, więc zachęcamy Cię do wypróbowania tego w przeglądarce.

To jest Jacze

Rysunek 4.10. Efekty przejścia

Listing 4.15 pokazuje kod potrzebny do uzyskania efektu związanego z przesunięciem kursora myszy.
Docelowy kolor animacji jest zdefiniowany w deklaracji : hover. Bez żadnego przejścia kolory natychmiast
zmieniłyby się na docelowe, ale dla uzyskania efektu stopniowej zmiany koloru używasz właściwości
tran s ; t ; on (oraz prefiksów producentów dla WebKit, Mozilli i Opery) . Kluczowy w tym przypadku
fragment CSS-a - tran s ; t ; on : a l l 1s ease- ; n-przekazuj e przeglądarce, by animowała wszystkie
właściwości (background i co l or) i użyła funkcji czasowej ease- ; n, oraz informację, że całkowity czas
trwania przej ścia to j edna sekunda ( 1 s ) . Możesz opcjonalnie umieścić wartość opóźnienia, tak j ak
w tran s ; t ; on : a l l 1s ease- ; n 1s, opóźniaj ącą rozpoczęcie przej ścia oraz moment, w którym element
powraca do swojego domyślnego stanu.

Listing 4.15. Animowana zmiana koloru po najechaniu kursorem

a {
bae kground: #fff;
border-radi us : 5px;
d i spl ay: bl oe k ;
f l oat : l eft ;
paddi ng: 5px;
text-al i gn : center;
w i d t h : 125px;
-webk i t -trans i t i on : al l ls ease- i n ;
-moz-trans i t i on : al l ls ease- i n ;
-o-trans i t i on : al l l s ease- i n ;
trans i t i on : al l ls ease- i n ;

a : hover
bae kground: #000 ;
eol or: #fff;

Za pomocą przejść uzyskujesz zatem drobne, subtelne efekty, ale możesz również używać ich do
poprawienia ogólnych wrażeń użytkownika strony. Weź jako przykład galerię miniatur zdjęć z rysunku 4. 1 1 .
Używając kodu z listingu 4.16, dzięki kombinacji rotate i sca l e tworzysz ładny efekt wyświetlenia obrazka
w jego pełnym rozmiarze po najechaniu na niego kursorem, i to bez potrzeby użycia JavaScriptu, co pokazano
na rysunku 4.12.

Listing 4 . 1 6 . Animowane powiększenie obrazka p o najechaniu kursorem

i mg {
bae kground: #fff;
border: 1px sol i d #BFBFB F ;
d i spl ay: bl oe k ;
102 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

fl oat : l eft ;
hei ght : 125px ;
marg i n : ° 10px ° O ;
paddi ng: 5px;
w i d t h : 125px;
-webki t-box-s hadow: 2px 2px 4px rgba ( O , O , 0, 0 . 3) ;
-moz-box-shadow: 2px 2px 4px rgba (O , O , 0 , 0 . 3) ;
box-s hadow: 2px 2px 4px rgba ( O , O , 0 , 0 . 3 ) ;
-webk i t -trans i t i on : al l ls ease- i n-out ;
-moz-trans i t i on : al l ls ease- i n-out ;
-o-trans i t i on : al l ls ease- i n-out ;

i mg : hover {
-webki t-transform: rotate (lOdeg) scal e (2) ;
-moz-transform: rotate (lOdeg) sca l e (2) ;
-o-trans form: rotate(lOdeg) scal e (2) ;
-ms-transform: rotate (lOdeg) sca l e (2) ;
trans form : rotate (lOdeg) scal e (2) ;
}


-.--

Rysunek 4.11. Galeria zdjęć przed przejściami

Rysunek 4.12. Obrazek z galerii zdjęć po najechaniu na niego kursorem


Tworzenie a n i macj i za pomocą CSS-a 103

PRZEPIS DLA ZAAWANSOWANYCH


Tworzenie animacji za pomocą CSS-a
w przeciwieństwie do przej ść, animacje nie wymagają aktywacji poprzez najechanie kursorem lub kliknięcie.
Obecnie animacj e są dostępne tylko w przeglądarkach opartych na Web Kit i w Firefoksie, ale wykorzystując
różne techniki javascriptowe, możesz uzyskać animacje także w innych przeglądarkach. Tabela 4.5 pokazuje,
które wersje każdej z przeglądarek obsługują animację CSS.

Tabela 4.5. Obsługa animacji CSS w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 13.0+
Fi refox 5 . 0+
Internet Explorer
iOS Safari 4.0+
Opera
Safari 5 . 0+

W pierwszym, prostym przykładzie przesuniesz obrazek z lewej strony ekranu do prawej i jednocześnie
go obrócisz. Pokazywanie zrzutu ekranu z tego przykładu nie ma większego sensu, więc otwórz przeglądarkę
Chrome, Firefox albo Safari i uruchom kod z listingu 4.17.

Listing 4.17. Animacja CSS

/* CSS */
div {
fl oat : l eft ;
hei ght : 100%;
pos i t i on : rel ati ve;
width: 100% ;
}
i mg {
pos i t i on : absol ute;
-webki t-anima t i on-name : move l t ;
-webki t-anima t i on-durati on: 5 s ;
-webki t-animat i on - i terat i on-count : i nfi n i te ;
-webki t-animat i on - t i m i ng-func t i o n : l i near;
-moz-animat i on-name : move l t ;
-moz-animat i on-durat i on : 5s ;
-moz-animat i on - i terat i on-count : i nf i n i te ;
-moz-animat i on-t i mi ng-funct i o n : l i near;
animat i on-name : move l t ;
animat i on-durati on: 5 s ;
animat i on - i terat i on-count : i nfi n i te ;
animat i on - t i m i ng-func t i o n : l i near;
}
@-web k i t - keyframes movelt
from {
l eft : O ;
-webki t-transform : rotate (Odeg) ;
}
to {
l eft : 100%;
-webki t-transform : rotate (360deg) ;
104 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

@-moz- keyframes movelt


from {
l eft : O ;
-moz-transform : rotat e ( Odeg) ;
}
to {
l eft : 100%;
-moz-transform : rotate (360deg) ;
}
}
keyframes movelt
from {
l eft : O ;
transform : rotate (Odeg) ;
}
to {
l eft : 100%;
transform : rotate (360deg) ;
}
}
/* HTML */
<di v>
< img src = " beac h . j pg " wi dth= " 2 5 0 " he i ght= " 188 " a l t = " Pl ai a " >
</d i v>

Przy selektorze i mg znajdują się cztery właściwości CSS dotyczące animacji. Są powtórzone, ponieważ
muszą się jawnie odwoływać do przedrostków -web k i t- i -moz-. Na końcu umieść nazwy właściwości bez
przedrostków z myślą o dniu, w którym wszystkie przeglądarki będą obsługiwać animacje CSS:
• -web k i t/moz -an i mat i on-name - nazwa animacji, której chcesz użyć;
• -web k i t/moz -an i mat i on-durat i on - jak długo animacja będzie trwać;
• -web k i t/moz -an i mat i on- i terat i on -count - ile razy animacja będzie powtórzona;
• -web k i t/moz -an i mat i on-t imi ng-funct i on rodzaj animacji; wybierz spośród ease, l i near,
-

ease- i n, ease-out, ease- i n -out i cub i c-bez i er, którą można dostosowywać.

Teraz zdefiniuj animację @-webki t/moz- keyframes Move I t . Jest to prosta animacja, więc zacznij od
właściwości from, a skończ na wartości to. Wewnątrz tych właściwości użyj zwykłego CSS-a, by przemieścić
oraz obrócić obrazek. W tych animacjach można zmienić prawie każdą właściwość CSS, masz więc dużo
różnych możliwości.
Korzystanie z właściwości from i to to j eszcze nie wszystko. Możesz również umieszczać klatki kluczowe,
używaj ąc procentów, co pozwoli Ci na większą elastyczność. Rysunek 4 . 1 3 pokazuj e baner, który znajduje
się w prawym górnym rogu strony. Dzięki użyciu kodu z listingu 4.18 kolor tła zmienia się płynnie, a klatki
kluczowe są umieszczone co 25 procent czasu animacji.

Listing 4. 18. Animowany baner

p {
bac kground: #000 ;
col or: #fff;
fon t : bol d 20px Tahoma , Genev a , sans-ser i f ;
padd i ng : lOpx ;
Tworzenie a n i macj i za pomocą CSS-a 105

pos i t i on : absol ute;


r i g ht : -65px;
text-al i gn : center;
top : 75px ;
w i d t h : 300px;
-webk i t -transform: rotate (45deg) ;
-moz-transform: rotate (45deg) ;
-o-transform: rotate (45deg) ;
-ms-transform: rotate (45deg) ;
transform: rotate (45deg) ;
-webki t-anima t i on-name : gl ow;
-webki t-anima t i on-durati on: 5 s ;
-webki t-animat i on - i terat i on-count : i nfi n i te ;
-webki t-animat i on - t i m i ng-func t i o n : ease- i n ;
-webki t-bac kface-v i s i b i l i ty : h i dden ;
-moz-animat i on-name : g l ow ;
-moz-animat i on-durat i on : 5s ;
-moz-animat i on - i terat i on-count : i nf i n i te ;
-moz-animat i on-t i mi ng-funct i o n : ease- i n ;
-moz-bac kface-vi s i b i l i ty : h i dden ;
animat i on-name : gl ow;
animat i on-durati on: 5 s ;
animat i on - i terat i on-count : i nfi n i te ;
an i mat i on - t i m i ng-func t i o n : ease- i n ;
bac kface-vi s i b i l i ty : h i dden ;
}

@-web k i t - keyframes gl ow {
0% {
bac kground: # FOO ;
}
25% {
bac kground: #06C ;
}
50% {
bac kground: #000 ;
}
75% {
bac kground: #06C ;
}
100% {
bac kground: # FOO ;
}
}

@-moz- keyframes g l ow
0% {
bac kground: # FOO ;
}
25% {
bac kground: #06C ;
}
50% {
bac kground: #000 ;
}
75% {
bac kground: #06C ;
}
100% {
bac kground: # FOO ;
106 Rozdział 4. Nowe techn i ki dotyczące u kład u i styl izacj i w CSS3

keyframes g l ow
0% {
bac kground: # FOO ;
}
25% {
bac kground: #06C ;
}
50% {
bac kground: #000 ;
}
75% {
bac kground: #06C ;
}
100% {
bac kground: # FOO ;
}
}

Rysunek 4.13. Animowany kolor tła

Wskazówka
Przejścia i a n i m acje m ają wiele zastosowa ń . Nie tyl ko pozwalają dodawać faj n e efekty do stron
i nternetowyc h , ale mogą być ta kże w prosty s posób tworzone i poprawiane przez proje ktantów
i program istów. Po kaza l iśmy w tym rozdziale jedyn ie proste przykłady, by pobudzić Twój a petyt,
a od C iebie zależy, jak je wykorzystasz. Dużym możliwościom towarzyszy jednak duża odpowiedzialność,
więc staraj się nie przesadzać z rozma itym i efekta m i , pon i eważ mogą one szybko zam ienić faj ną
witrynę w kiczowatą. Zbyt wiele przejść i a n i m acj i może równ ież wpłynąć n a wyd ajność przegląd a r k i .
Sugerujemy s pędzić trochę czasu na poszu kiwa n i u w internecie przykładów i badaniu s posobów,
w j akie i n n i projektanci i program iści użyli CSS3 do u l e pszania witryn .

Podsumowanie
W tym rozdziale poznałeś kilka nowych możliwości CSS-a: zapytania o media, własne czcionki, gradienty,
transformacje, przejścia oraz animacj e. Istnieje o wiele więcej funkcji niż te, które opisaliśmy; dotknęliśmy
jedynie powierzchni. Jeśli j esteś zainteresowany CSS-em, sugeruj emy, żebyś kontynuował poszukiwania
i naukę, ponieważ istniej e wiele innych technik i sugerowanych praktyk dotyczących użycia niektórych
spośród tych nowych możliwości. A zatem zgłębiaj CSS i baw się nim.
5
Formularze internetowe HTML5

W
tym rozdziale poznasz formularze internetowe HTMLS oraz ich nowe funkcj onalności dostępne
teraz dla twórców stron internetowych. Oczywiście nie wszystkie te nowe możliwości są w pełni
obsługiwane przez wszystkie przeglądarki, ale jak zobaczysz w przepisach w tym rozdziale, mimo to nieźle
działają i wyglądają. Przy tworzeniu formularzy do wyboru masz ponad dziesięć nowych typów elementu
i nput, kilka nowych atrybutów oraz parę nowych trików CSS-owych.

Walidacja
Mimo udostępnienia kilku nowych typów elementu i nput, bez wątpienia najważniejszą nową możliwością
oferowaną przez HTML jest wbudowana walidacja formularzy. Dawniej trzeba było używać kodu po stronie
serwera, takiego j ak PHP albo CI, do sprawdzania treści przesłanego formularza, a następnie musiałeś
zwracać stronę i wyświetlać użytkownikowi błędy. Alternatywnie można było używać jakiegoś wymyślnego
JavaScriptu do sprawdzania na bieżąco treści wprowadzanych danych i informowania użytkownika, czy
wystąpiły błędy.
Przy użyciu HTMLS walidacja formularzy jest prosta. Możesz dać użytkownikom wbudowaną walidację,
dzięki której otrzymują informację zwrotną, w momencie gdy próbują wysłać formularz, co powoduje, że
formularze łatwiej jest wypełniać, i zwiększa szanse wysłania poprawnych informacji. Oczywiście będziesz
potrzebował j akiegoś kodu po stronie serwera do przetworzenia formularzy.
W przepisach w tym rozdziale do większości elementów formularzy dodajemy atrybut requ i red. Robimy
tak, byś podczas testowania mógł zobaczyć, jakie komunikaty dotyczące walidacji pokazuje przeglądarka
G eśli je pokazuje). Ten atrybut mówi przeglądarce, że formularz nie może być wysłany, zanim element nie
zostanie wypełniony.

Typy wejściowe HTML 4


Jedynie napomkniemy o elementach formularzy, które były używane od lat przy tworzeniu stron
internetowych. Są one nadal podstawowymi i zupełnie prawidłowymi elementami formularzy i nie
są zastępowane innymi. W HTMLS nadal pozostają w użyciu przy tworzeniu formularzy.
Zasadniczo masz do dyspozycji elementy form, fi e l dset (fi e l dset jest elementem grupującym powiązane
pola formularza) oraz zwykłe elementy formularzy, co daje Ci możliwość stosowania kontrolek formularzy
pokazanych na rysunku 5 . 1 .
108 Rozdział 5. Form u l a rze internetowe HTML5

L egenda elementu fieldset - podp'1'<Itytuł tego zb'lOru POI


" l-

Zwykłe pole wejściowe zwykłe pole wejściowe

El Jestem przyciskiem wyboru

� Jestem przyciskiem opcji

Prześlij plik [ Przeglądaj_ I


Hasło ••••••••••

Pole tekstowe
To j est pole tekstowe

Wybór Opcja 1 �

[ Prześlij I
[ Wyczyść I

Rysunek 5 . 1 . Kontrolki formularzy przed HTML5

Listing 5 . 1 pokazuje HTML wyświetlaj ący rysunek 5 . 1 . Zauważ, że do sformatowania tego formularza
zostały użyte lista nieuporządkowana ( u l ) i elementy listy ( l i ) . Nie j est to j edyny sposób organizacji
formularza, ale dzięki niemu nie musisz niepotrzebnie używać w tym kodzie <br l>.

Listing 5 . 1 . Strona zawierająca różne składniki formularzy

<form>
<fi el dset>
<l egend>Legenda el ementu f i el dset - podp i s/tytut tego zbi oru pól </l egend>
<ul >
<l i ><l abel for= " normal " >Zwykte pol e wej ś c i owe</l abe l > <i nput i d= " normal " type= " text " val ue= " zwykte
"'po l e wej śc i owe" /></l i >
<l i><i nput i d= " c hec kbox" type= " c he c kbox" / > <l abel for= " chec kbox ">Jestem przyc i s k i em
"'wyboru</ l abel ></l i >
< l i><i nput i d= " rad i o " type= " rad i o" /> <l abel for= " rad i o " >Jestem przyc i s k i em opcj i </l abel ></l i >
<l i ><l abel for= " fi l e " >Prześl i j pl i k</l abel > < i nput i d= " f i l e " type= " fi l e" /></1 i >
<l i ><l abel for= " password">Hasto</l abel > <i nput i d= " password" type= " password" val ue= "mypassword" /></l i >
<l i ><l abel for= " textarea ">Pol e tekstowe</l abe l > <textarea i d= " textarea" rows= " 5 " col s= "40">To jest
"'po l e tekstowe</textarea></l i >
< l i ><l abel for= " se l ec t " >Wybór</l abe l >
<sel ect i d= " sel ec t " >
<opt i on>Opcj a l</opt i on>
<opt i on>Opcj a 2</opt i on>
<opt i on>Opcj a 3</opt i on>
<opt i on>Opcj a 4</opt i on>
</sel ect></l i >
<l i><i nput type= " subm i t " val ue= " Prześ l i j " /></l i >
<l i><i nput type= " reset" val ue = " Wyczyś ć " /></1 i >
<fu l >
</f i e l dset>
</form>
Tworzenie form u larza do d anych kontaktowych 109

Istnieje kilka nowych typów wejściowych w HTML5, które są użyteczne przy tworzeniu albo modyfikacji
starych formularzy. Ułatwią Ci one jako programiście pracę, lecz pomogą również użytkownikowi wprowadzać
poprawne dane.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie formularza do danych kontaktowych
Na listingu 5.2 użyliśmy trzech spośród nowych typów wejściowych do gromadzenia danych kontaktowych
użytkownika: ema ; l , te l i url . Spójrz na kod. Typy wejściowe są zaznaczone pogrubioną czcionką i są
omówione dalej w kolejnych podpunktach.

Listing 5 . 2 . Formularz kontaktowy z nowymi elementami formularzy HTML5

<form>
<fi el dset>
<l egend>Dane kontaktowe<jl egend>
<ul >
<l i >
<l abel for= " ema i l " >E-ma i l <jl abe l >
<i nput requi red type= " emai l ll i d= " emai l ll name= I I emai l II />
<jl i >
<l i >
<l abel for= " tel " >Numer tel efonu<jl abe l >
<i nput requi red type= " tel " i d= " tel " name= " t e l " j>
<jl i >
<l i >
<l abel for= " url " >W i tryna<jl abel >
<i nput requi red type= " url ll i d= " url ll name= " url ll />
<jl i >
<ju l >
< i nput type= " subm i t " val ue= " Prześ l i j to" j>
<jf i e l dset>
<jform>

input type="email"
Typ wejściowy ema ; l mówi przeglądarce, że treść tego pola powinna wyglądać jak adres e-mail. Na listingu
5.3 przy polu wejściowym ema ; l umieściliśmy atrybut req u ; red. Poinformuj e on kompatybilne przeglądarki,
by sprawdziły treść tego pola przed wysłaniem formularza. To, czy adres e-mail rzeczywiście istniej e, nie j est
sprawdzane.

Listing 5 . 3 . Typ wejściowy email

<form>
<l abel for= " emai l ">E-mai l <jl abel >
< i nput requ i red type="emai l ll i d= " emai l ll name = " ema i l ll />
< i nput type= " subm i t " j>
<jform>
110 Rozdział 5. Form u l a rze internetowe HTML5

Tabela 5 . 1 . Obsługa typu wejściowego email w przeglądarkach i urządzeniach

Android 2 . 3+*
Chrome 10.0+
Fi refox 4.0+
Internet Explorer 9 . 0+*
i OS Safa ri 4.0+*
Opera 10.0+
Safari 5 . 0+*
* brak walidacji

Coraz szybszy rozwój przeglądarek, których producenci nieustannie starają się wyprzedzić konkurencję,
sprawił, że pomiędzy nimi istnieją różnice w sposobie obsługiwania walidacji. Testy przedstawione w tabeli
5.2 pokazują różnice w sposobie obsługi formularzy, ich walidacji i informacji zwrotnych. Wcześniejsze
wersje zwracają komunikaty walidacji inne od tych pokazanych w testach.

I
Uwaga
Być może zastanawiasz się, czy m ożesz n ad awać style inform acjom zwrotnym po kazywanym przez
formu l arz. Cóż, w pewnym sensie możesz. Służący do tego CSS omówimy w d a lszej części rozdziału .

Tabela 5 . 2 . Testy walidacji typu wejściowego email

Firefox 11.0: Opera 11.62: Chrome 18:


Wprowadzone Odpowiedź czy formularz Odpowiedź czy formularz Odpowiedź czy formularz
dane Firefoksa 11.0 został Opery 11.62 został Chrome 18 został
wysłany? wysłany? wysłany?
puste pole " Proszę wypelnić Nie "To pole jest Nie "Wypelnij to Nie
to pole . " wymaga ne" pole . "

test " Proszę Nie "Wpisz Nie "Wprowadź Nie


wprowadzić prawidlowy adres e-m a i l . "
adres e-ma i l . " adres e-m a i l "

test@test.com Nie ma blędu Tak Nie ma blędu Tak Nie ma blędu Tak

test@test Nie ma blędu Tak Nie ma blędu Tak N i e ma blędu Tak

input type=" tel"


Podobnie j ak w przypadku nowego typu wejściowego do wyszukiwania, typ wejściowy te l jest wyświetlany
jak zwykłe pole tekstowe (patrz rysunek 5.2). W tym polu akceptowane są dowolne znaki, nie tylko liczby,
ponieważ numery telefonów mogą mieć znaki nienumeryczne, takie j ak + lub ( . Jeśli chcesz, by pole
akceptowało tylko liczby, możesz użyć atrybutu pattern, który jest opisany w dalszej części rozdziału.

Nillller telefonu L...I


________ --'

[ Wyślij l
Rysunek 5 . 2 . Element input type="tel" w Operze. Wygląda jak zwykłe pole tekstowe
Tworzenie form u larza do d anych kontaktowych 111

Tabe la 5.3. Obsługa typu wejściowego tel w przeglądarkach i urządzeniach

Android 2 . 3+*
Chrome 10.0+
Fi refox 4.0+
Internet Explorer 9 . 0+*
iOS Safari 4.0+*
Opera 10.0+
Safari 5 . 0+*
* brak walidacji

input type="u rl"


Częstym wymaganiem w formularzach internetowych, takich jak formularz komentarzy do bloga, jest podanie
przez użytkownika adresu URL jego własnej strony internetowej . Pytanie o adres URL w HTML5 jest łatwe
i przyjemne, j ak pokazuje listing 5.4.

Listing 5.4. Typ wejściowy url

<l abel for= " url " >Wi tryna<jl abe l >< i nput requ i red type= " url " i d= " url " name= " url " j>

Tabe la 5.4. Obsługa typu wejściowego url w przeglądarkach i urządzeniach

Android 2 . 3+*
Chrome 10.0+
Fi refox 4.0+
Internet Explorer 9 . 0+*
iOS Safari 4.0+*
Opera 10.0+
Safari 5 . 0+*
* brak walidacji

Treść typu wej ściowego url powinna być prawidłowym URL-em. Znowu, podobnie jak w przypadku
typu ema ; l , to, czy adres rzeczywiście istnieje, nie jest sprawdzane (można to zrobić za pomocą dodatkowego
kodu skryptowego). Tradycyjnym URL-em będzie adres strony internetowej, taki jak http://mojawitryna.com.
Firefox 10 nie akceptuje mojawitryna.com ani www.mojawitryna.com; wymaga adresu zawierającego http://.
Jednak Opera 1 1 automatycznie zamienia mojawitryna.com na prawidłowy adres http://mojawitryna.com,
który pozwala wysłać formularz.
W większości przypadków zawartością tego pola będzie http://mojawitryna.com; jednak zaakceptuje ono
również inne rodzaje adresów URL:
• Jtp://użytkownik:hasło@serwer,
• javascript:window.alert (należy z tym uważać; możesz napisać swoją własną walidację,
aby nie dopuścić do przesyłania JavaScriptu),
112 Rozdział 5 . Form u l a rze internetowe HTML5

• file://serwer/ścieżka,
• tel:12345.

Ze względu na te dodatkowe typy adresów URL w chwili obecnej formularz przeszedłby walidację
z zawartością hello:world. Jest tak dlatego, że większość przeglądarek ma swoj e własne typy adresów URL,
jak na przykład Firefox, który ma about:config. A zatem obecnie wszystko, co zawiera dwukropek w URL-u,
przejdzie walidację. W przyszłości, gdy walidacja w przeglądarkach zostanie udoskonalona, może się to zmienić.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie formularza wyszukiwania
przy użyciu input type="search"
Typ wejściowy search powinien być wypełniony frazą do wyszukania. Wygląda jak zwykłe pole tekstowe,
ponieważ w istocie jest to po prostu normalne pole tekstowe. Nie ma w nim naprawdę nic szczególnego,
ale pozwala przeglądarkom rozpoznawać, że jest to pole wyszukiwania. Listing 5.5 pokazuj e, w j aki sposób
używać go w kontrolce wyszukiwania.

Listing 5 . 5 . Typ wejściowy search

<form rol e= " searc h " >


<l abel for= " searc h " >Wys z u k i wana fraza</l abe l >
<i nput req u i red type= " searc h" i d= " search " name= " search " />
<i nput type= " s ubmi t " val ue= " Prześl i j " />
c/form>

Tabela 5 . 5 . Obsługa typu wejściowego search w przeglądarkach i urządzeniach

Android 2 . 3+
Chrome 10.0+
Fi refox 4.0+
Internet Explorer 9 . 0+
iOS Safari 4.0+
Opera 10.0+
Safari 5 . 0+

Ciekawym dodatkiem w przeglądarkach opartych na silniku WebKit (takich jak Safari i Chrome) jest
atrybut resu l ts, który daj e dodatkową funkcjonalność:
<l abel for= " searc h " >Wys z u k i wana fraza</l abe l >
<i nput requ i red type= " searc h" resul ts= " 5 " i d= " searc h" name= " searc h" />

Powyższy kod powoduje pokazanie pola wyszukiwania zaprezentowanego na rysunku 5.3. Zwróć uwagę
na ikonę lupy dodaną do pola wejściowego, która wyświetli poprzednio wyszukiwane frazy (jeśli takie istnieją).
Niestety, nie można jeszcze nadawać stylów takim podpowiedziom za pomocą CSS-a lub J avaScriptu.
Tworzenie kontrolek kalendarza i czasu 113

Wyszukiwana fraza 0..· 1 [ Prześlij I


L:mm:ł1!!El�.
lml
Rysunek 5.3. Element input type= "search " z informacjami i szczegółami
specyficznymi dla przeglądarki opartej na silniku WebKit

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie kontrolek kalendarza i czasu
Problemem, z którym twórcy stron internetowych mają często do czynienia, jest stworzenie widżetu kalendarza
pozwalającego użytkownikom wybierać datę, tak aby nie musieli jej wprowadzać samodzielnie. Tworzenie
widżetu zawsze wymagało J avaScriptu, ale teraz się to zmienia. W tym przepisie poznasz różnego rodzaj u
nowe typy pól wejściowych, które tworzą różne kontrolki kalendarza, a dla przeglądarek, które nie udostępniają
widżetu kalendarza (spójrz do rozdziału 3.), istnieje kilka rozwiązań awaryjnych.

Tabe la 5 . 6 . Obsługa typu wejściowego datetime w przeglądarkach i urządzeniach

Android *

Chrome *

Fi refox *

Internet Explorer *

iOS Safari 5 . 0+
Opera 10.0+
Safari *

* brak widżetu kalendarza. ale zamiast niego


pojawia się awaryjnie pole tekstowe

input type="d atetime"


Nowy typ wejściowy dat et i me z pewnością będzie dla wielu ulubionym. Jeszcze nie wiadomo, co kilku
głównych producentów przeglądarek zdecyduje się z nim zrobić, ale Opera natywnie generuj e znakomity
kalendarz będący widżetem wyboru daty, pokazany na rysunku 5.4.
Wcześniej musiałeś użyć J avaScriptu, aby udostępnić tak złożoną kontrolkę. Ponieważ teraz przeglądarki
mogą coś takiego obsługiwać, możliwa stała się integracja z innymi usługami, takimi jak kalendarz wydarzeń
Facebooka albo kalendarz programu Outlook. Ponadto, ponieważ to przeglądarka obsługuje kalendarz, jeśli
system użytkownika jest skonfigurowany w innym języku, wyświetli go w nim (na przykład Novembre po
włosku), w przeciwieństwie choćby do widżetu kalendarza j Query (http://docs.jquery.com/UI/Datepicker).

input type="d atetime-I ocal"


Niemal identyczny z poprzednim polem wejściowym datet i me, typ datet ime-l oca l również generuj e
kalendarz, z tą drobną różnicą, że nie ma etykiety "UTC" po prawej stronie, więc nie j est z nim związana
strefa czasowa (patrz rysunek 5.5).
114 Rozdział 5. Form u l a rze internetowe HTML5

Data i czas • n � UTC

[ Prześlij I GJ Kwi ... G � �


Pn Wt Śr Cz Pt Sa N
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 1 1 12 1 3 14 1 5
1 6 1 7 1 8 1 9 2 0 2 1 22
23 24 25 26 27 28 29
30 1 2 3 4 5 6
I Dzisiaj I
Rysunek 5.4. Element input type="datetime " w Operze 11
zapewnia użytkownikowi widżet wyboru daty z kalendarza

Data/czas Ooka1ny)
I Prześlij I
Rysunek 5 . 5 . Element input type="datetime-Iocal" wyświetlony w Operze 11

input type="d ate"


Na rysunku 5.6 widać, że typ wej ściowy date jest podobny do typu wejściowego datet ; me pokazanego
na rysunku 5.4, ale tutaj kontrolka nie wyświetla pola czasu.

Którego dnia wyjeżdżasz? =,---_


- -==-_==

[ Prześlij I GJ Luty G��


Pn Wl Śr Cz Pl Sa N
30 31 1 2 3 4 5
6 7 8 9 1 0 1 1 12
13 14 15 1 6 1 7 18 1 9
20 21 22 23 24 25 26
27 28 29 2 3 4
5 6 7 9 10 1 1
Dzisiaj I
Rysunek 5 . 6 . Element input type= "date " wyświetlony w Operze 11

input type=" time"


Typ wejściowy t ; me pozwala użytkownikowi wprowadzić czas w formacie 24-godzinnym. Obecnie tylko
Opera obsługuje ten element. Rysunek 5.7 pokazuje, j ak typ wejściowy t ;me wygląda w Operze.

Która jest godzina? D�


I Prześlij I
Rysunek 5 . 7 . Element input type= "time" wyświetlony w Operze 11

Użytkownik może albo wpisać cyfry, na przykład 22: 1 1 , albo użyć przycisków do przewij ania czasu
dostępnych w Operze.
Tworzenie kontrolek kalendarza i czasu 115

input type="month"
Typ wejściowy month wyświetla kalendarz przedstawiony na rysunku 5.4, ale pozwala wybierać jedynie
miesiące. Wartością typu wej ściowego j est wtedy RRRR-MM. Na rysunku 5.8 można w polu miesiąca
wybrać inny miesiąc niż grudzień; można również przejść do poprzedniego lub następnego miesiąca.

Jaki jest miesiąc?


[ Prześlij l =c--
GJ -
GrL
-=�-== 0 ��
Pn Wt Ś r Cz Pl Sa N
29 30 1 2 3 4 5

6 7 8 9 10 1 1 1 2

13 14 1 5 1 6 1 7 1 8 19

2 0 2 1 22 2 3 24 25 26

2 7 2 8 2 9 30 3 1 1 2

3 4 5 6 7 8 9

I Dzisi aj I
Rysunek 5 . 8 . Element input type="month " wyświetlony w Operze 11

input type="week"
Bardzo podobny do typów wej ściowych date i month, week pozwala użytkownikowi wybrać tylko któryś
tydzień w roku. Wartością typu wej ściowego j est RRRR- WOO. Na rysunku 5.9 można wybrać tydzień,
a w lewej kolumnie udostępniony jest numer tygodnia.

� ---K-----
Który jest tydzień?
I Prześlij ) "'GJ
= ='"1=2=0=
wie cien' ---0=0 12 1 �
= =

Tydzi Pn Wt Śr Cz Pt Sa N
13 26 27 28 29 30 31

14 2 3 4 5 6 7 8

15 9 10 11 12 1 3 14 1 5

16 1 6 1 7 1 8 1 9 2 0 2 1 22

17 23 24 25 26 27 28 2 9

18 30 1 2 3 4 5 6

Dzisiaj

Rysunek 5 . 9 . Element input type="week" wyświetlony w Operze 11

N akład anie ograniczeń na d aty i god ziny


Istniej ą dwa nowe atrybuty, mi n i max, o których opowiemy w dalszej części rozdziału. Mogą one być
wykorzystane do kontroli i ograniczania dat lub czasów w widżetach. W przypadku daty, jeśli chcesz mieć
pewność, że użytkownik nie będzie mógł wybrać daty z przeszłości, możesz dodać wartość atrybutu mi n
w następuj ącym formacie: RRRR-MM-DD. Tak samo jest w przypadku wartości atrybutu max, który
uniemożliwia użytkownikom wybieranie daty ze zbyt odległej przyszłości. W przypadku typu wejściowego
t ime format miałby postać HH:MM.
116 Rozdział 5. Form u l a rze internetowe HTML5

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie pola do wprowadzania liczby
Typ wejściowy n umber jest używany do wprowadzania liczb. Akceptuje tylko liczby; w przypadku innych
znaków zwraca błąd walidacji. Dopuszcza atrybuty mi n, max i step, dzięki czemu możesz ograniczyć zakres
liczb do takich, które odpowiadaj ą Twoim potrzebom. Atrybut step pozwala określić wartości przyrostu,
które mogą być wprowadzone.

Tabe la 5 . 7 . Obsługa pola wprowadzania liczby w przeglądarkach i urządzeniach

Android *

Chrome 11.0+
Fi refox *

Internet Explorer *

iOS Safari *

Opera 10.0+
Safari 5.1
* brak pola d o wprowadzania liczby. ale zamiast
niego pojawia się awaryjnie pole tekstowe

Wyświetlając kod z listingu 5.6, Opera tworzy kontrolkę, która pozwala użytkownikowi przełączać się
między liczbami (patrz rysunek 5.10). Liczba może być ujemna, a jeśli nie podasz maksimum lub minimum,
może być dowolnie duża. Przy użyciu atrybutu step możesz precyzyjniej określić wymagania. Na rysunku
5 . 1 0 użytkownik może wybierać kolejne liczby od O do 10 (mi n i max ) co 0,5. A zatem może wybrać O; 0,5; 1 ;
1 ,5 i tak dalej . Użytkownik może albo użyć udostępnionych kontrolek, albo wprowadzić liczbę z klawiatury.

Listing 5 . 6 . Typ wejściowy number z określoną wartością kroku

< i nput m i n= II Q II max= II IO II step= II O . 5 11 requi red type= lI number" i d= lI numberll name= II number" />

Podaj liczbę I 2.51 �


I
Prześl j
i l
Rysunek 5 . 10 . Element input type="number" wyświetlony w Operze 11

Ponieważ określiłeś zakres kroku, wszystkie wartości spoza zakresu spowodują błędy walidacji, więc
wpisanie liczby 9,99, jak na rysunku 5 . 1 1 , jest niedozwolone.

Podaj liczbę I A9.99 1�


W tym formu l arzu wartość 9.99 jest
nie d ozwolo n a. W po l u można wpi sać
tylko określone wartości .

Rysunek 5 . 1 1 . Element input type="number" z nieprawidłową liczbą (w tym przykładzie dozwolone


są tylko liczby całkowite lub mające wartość pięciu dziesiątych po przecinku)
Tworzenie próbn i ka kolorów 117

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie suwaka ( bez potrzeby użycia JavaScriptu)
Typ wejściowy range generuje kontrolkę suwaka. Nie posiada obszaru d o wpisywania tekstu przez
użytkownika i, tak jak typ wejściowy n umber, może korzystać z atrybutów mi n, max i step.

Tabe la 5.8. Obsługa suwaka w przeglądarkach i urządzeniach

Android *

Chrome 10
Fi refox *

Internet Explorer *

iOS Safari 5 . 0+
Opera 10.0+
Safari 5 . 0+
* brak suwaka. ale zamiast niego pojawia się
awaryjnie pole tekstowe

Możliwym zastosowaniem typu wej ściowego range może być sekcj a strony lub formularza w rodzaj u
"oceń moje coś tam" albo sterowanie głośnością odtwarzacza wideo lub audio.
Na listingu 5.7 pokazaliśmy jedynie HTML potrzebny do utworzenia prostej kontrolki range, a w jednym
z kolejnych rozdziałów dowiesz się, jak zrobić z niej działającą regulację głośności.

Listing 5 . 7 . Typ wejściowy range

cl abel for= " range">Głośnośćcj l ab e l >


< i nput m i n= II Q II max= II IO II step= II O . 5 11 requi red type= lI rangell i d= lI rangell name= lI rangell />

Mamy ograniczoną kontrolę nad tym, jak wygląda suwak, ponieważ obecnie wygląd kontrolek zależy od
przeglądarki. Możesz jednak określić wysokość i szerokość kontrolki zakresu. Jeśli ustawisz wysokość, która
jest większa niż szerokość, regulacja głośności wyświetli się pionowo zamiast domyślnego układu poziomego.
W chwili pisania tej książki ten typ wejściowy obsługują Opera, Chrome i Safari, co ilustruj e rysunek 5 . 1 2 .
Główną różnicą jest t o , że Opera domyślnie pokazuje podziałkę.

Głośność . . . . . 0-.-.-.-. -. Głośność �----iOf----


Rysunek 5 . 1 2 . input type= "range " w Operze 11 (po lewej) i Chrome 18 (po prawej)

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie próbnika kolorów
Jak pokazuje rysunek 5 . 1 3, < i nput type= "col or"> daje użytkownikowi możliwość wyboru podstawowych
kolorów i pozwala na wprowadzenie wartości szesnastkowej (#1 2ffOO) albo użycie próbnika kolorów
podobnego do takiego, jaki jest wykorzystywany w wielu pakietach oprogramowania. Jedyną przeglądarką
118 Rozdział 5. Form u l a rze internetowe HTML5

••••

#000000

Inny...

Kolor

� podstawowe:
. ii .i•
• iu •
• •• • ••••
• • • •
• •• • ••••
I lIll • • · · rJ . i
KoiOIy rie�andardowe:
.iiiiiii
iiiiiiii
• Qdc.:
Ma•. : O
1 60 Cz�. :

�.: O
O

0!'fB.II kololy noestandardowe » KolorlEelny iaskr.: O Niell. : O


�� I Qodaj do kołotów riestandardowych

Rysunek 5 . 13. input type= "color" w Operze 11

desktopową, która obecnie obsługuje ten przydatny typ wej ściowy, jest Opera 1 1 (choć nowe przeglądarki
BlackBerry również obsługują próbnik kolorów) .

Tabela 5 . 9 . Obsługa próbnika kolorów w przeglądarkach i urządzeniach

Android *

Chrome *

Fi refox *

Internet Explorer *

iOS Safari *

Opera 10.0+
Safari *

* brak próbnika kolorów. ale zamiast niego


pojawia się awaryjnie pole tekstowe

PRZEPIS DLA POCZĄTKUJĄCYCH


Wyświetlanie wyników za pomocą elementu output
Element output wykorzystuje JavaScript do wyświetlania wyników, zazwyczaj obliczeń lub skryptu. Może
wyświetlić wynik w kalkulatorze lub umieścić na stronie jakąś dodatkową funkcjonalność, taką jak wyświetlenie
imienia użytkownika. Jeśli nie musisz umieszczać w nim żadnych dodatkowych treści, może być znacznikiem
zamkniętym; masz więc wybór pomiędzy <outputj> albo <output><joutput>.
Użycie tekstu zastępczego form u l a rza 119

Tabela 5 . 10. Obsługa elementu output w przeglądarkach i urządzeniach

Android
Chrome 10.0
Fi refox 4.0
Internet Explorer
iOS Safari 4.0
Opera 10.0+
Safari 5.0

W przykładzie pokazanym na rysunku 5 . 1 4 podajemy wartości typu wej ściowego range w trakcie
przesuwania suwaka. Wartość domyślna jest pusta, ale gdy użytkownik przesuwa suwak, wartość elementu
output zmienia się i jest pokazywana użytkownikowi w czasie rzeczywistym. Listing 5.8 przedstawia kod
tego przykładu.

Listing 5 . 8 . Element output

<form oni nput = " output . val ue=range . val ue">


<l abel for= " range">Głośność<jl abel ><i nput m i n= " O" max= " lO " step= " O . 5 " requi red type = " range" i d= " range"
4-name= " range" />
<output name="output"></output>
<jform>

Głośność . . . . . . . [T.---;-. 7
Rysunek 5 . 14. Element output użyty do wyświetlenia wartości typu wejściowego range w Operze 11

PRZEPIS DLA POCZĄTKUJĄCYCH


Użycie tekstu zastępczego formularza
Jak już się zorientowałeś, w HTML5 istniej e wiele nowych typów wejściowych, ale również mnóstwo nowych
atrybutów, których możesz używać razem z nowymi typami wejściowymi, by poprawić swoj e formularze.
Tekst zastępczy to tekst wyświetlany wewnątrz pola tekstowego po załadowaniu formularza. Gdy
użytkownik kliknie w polu lub przejdzie do niego za pomocą klawisza tabulacji, tekst znika. Zazwyczaj
daje on wskazówkę dotyczącą tego, co wpisać w polu, albo sugeruje format tego, co powinno być wpisane.
Przykładem może być formularz wyszukiwania z tekstem "Wyszukaj na stronie" albo, jak widziałeś w przypadku
typu wejściowego url , podpowiedź, która sugeruje użytkownikowi rozpocząć URL od http:// (rysunek 5.15).

Witryna http/lmojawitryna.pl [ Prześlij I Witryna [ ] [ Prześ lij I


Rysunek 5.15. Atrybut placeholder w Safari przed aktywacją pola i po niej

Wcześniej uzyskanie takiego efektu wymagało użycia J avaScriptu, lecz teraz możesz pozwolić przeglądarce,
by zrobiła to za Ciebie.
120 Rozdział 5. Form u l a rze internetowe HTML5

Tekst zastępczy może być słabo widoczny, ponieważ obecnie kolor tekstu j est domyślnie jasnoszary,
co nie daj e wystarczającego kontrastu z domyślnym kolorem tła. Możesz zmienić kolor tekstu w Mozilli
i przeglądarkach opartych na silniku WebKit za pomocą następujących reguł CSS:
i nput : : -webki t - i nput-pl acehol der {col or: red ; }
i nput : -moz-pl acehol der { col or: red ; }

Tabela 5 . 1 1 . Obsługa atrybutu placeholder w przeglądarkach i urządzeniach

Android 2.2
Chrome 9 . 0+
Fi refox 4.0+
Internet Explorer
iOS Safari 4.0+
Opera 10.0+
Safari 4.0+

PRZEPIS DLA POCZĄTKUJĄCYCH


Autouzu pełnianie za pomocą atrybutu list
i elementu datalist
Element data l ; st jest w HTML5 nowością. W połączeniu z atrybutem l ; st jest on wykorzystywany w celu
udostępnienia predefiniowanej listy opcji (patrz rysunek 5 . 1 6), co powoduje, że proces tworzenia listy
przypomina samouzupełniający się formularz. Użytkownicy nie muszą wybierać predefiniowanych opcji;
jeśli chcą, mogą wpisać swoją własną odpowiedź.

Jaki jest Twój ulubiony sport? pl i Jaki jest Twój ulubiony sport? II

l Prześl ij I � ------------,
p iłka ręczna t Prześlij l �kosz
--------------�
ykówka koszykówka
piłka nożna szczypiorniak piłka ręczna
piłka n oż n a piłka n oż n a
h o kej hokej

Rysunek 5 . 16 . Element datalist użyty do uzyskania listy rozwijanej z uzupełnieniami

Tabela 5 . 1 2 . Obsługa elementu datalist w przeglądarkach i urządzeniach

Android *

Chrome *

Fi refox 4.0+
Internet Explorer *

iOS Safari *

Opera 10.0+
Safari *

* brak listy rozwijanej, ale zamiast niej pojawia


się awaryjnie pole tekstowe
Ś ledzenie postę pu wykonania zadania za pomocą elementu progress 121

Kod elementu data' ; st (pokazany na listingu 5.9) j est podobny do kodu dla elementu se' ect. Jednak
w przypadku elementu s e ' ect użytkownik nie może wpisać swoj ej własnej wartości. Z data' ; st nie ma
takiego problemu. Wykorzystując atrybut ' ; st, powiązuj esz z elementem data' ; st zwykły element ; nput
(może to być również typ ur' , ema ; ' , search albo te ' ) .

Listing 5 . 9 . Przykład użycia datalist

<l abel for= " sport ">Jaki j est Twój ul ubi ony sport?<jl abe l >
< i nput l i st=" sportl i st " type= " text" i d= " sport" name = " sport" j>
<datal i st i d= " sport l i st">
<opt i on l abel = " koszykówka" val ue=" koszykówka" j>
<opt i on l abel = " p i ł ka ręczna" val ue= " szczyp i orn i a k " j>
<opt i on l abel = " p i ł ka noina" val ue= " p i ł ka noina" j>
<opt i on l abel = " hokej " val ue=" hokej " j>
</datal i st>

W powyższym przykładzie mamy cztery opcje samo uzupełniania. Jedynie Firefox i Opera pokazuj ą
obecnie opcje auto uzupełniania, ale robią to nieco inaczej : Firefox pokazuje tylko opcje, które są podobne
do tego, co zostało wpisane, natomiast Opera pokazuje całą listę, gdy pole jest aktywne.
Element data' ; st mógłby być generowany z bazy danych lub wywołania AJAX (podobnie do funkcji
autouzupełniania wyszukiwarki Google) i dostarczać użytkownikowi, powiedzmy, 10 najbardziej popularnych
odpowiedzi na pytanie. Piękno tego nowego elementu i atrybutu polega na tym, że jeśli użytkownik korzysta
z przeglądarki, która nie wyświetla tego elementu HTMLS, nie posiada J avaScriptu albo wystąpił problem
z połączeniem ajaksowym, kontrolki formularza wyświetlają się jak zwykłe pole tekstowe, a użytkownik może
normalnie wpisywać tekst.

PRZEPIS DLA POCZĄTKUJĄCYCH


Ś ledzenie postępu wykonania zadania
za pomocą elementu progress
Nowy element progress służy do śledzenia statusu i stopnia wykonania zadania. Może być używany do
wyświetlania postępu pobierania danych, na przykład pliku lub ładowania danych z wywołania AJAX.
Element progress ma dwa opcjonalne atrybuty:
• va' ue - atrybut va' ue wskazuj e, j ak duża część zadania została ukończona;
• max atrybut max reprezentuje wartość oznaczaj ącą całkowite wykonanie tego zadania. Zarówno
-

zawartość atrybutu va' ue, jak i max powinna być wyświetlana wewnątrz elementu progress, tak aby
użytkownik miał informację zwrotną dotyczącą stanu zaawansowania wykonania zadania.

Następujący kod wyświetli element progress w przeglądarce Chrome (rysunek 5 . 1 7). Pasek postępu jest
podświetlony, dzięki czemu lepiej się prezentuje.
<p>Pobi erasz bardzo wainy pl i k , proszę czekać . <jp>
<progress val ue= " 4 5 " max= " lOO" >ukończono <span>45<jspan>%<jprogress>

W przeglądarkach, które nie obsługują elementu progres s, uzyskasz rezultat pokazany na rysunku 5 . 1 8
(zrzut ekranu z IE9).
122 Rozdział 5 . Form u l a rze internetowe HTML5

Pobierasz bardzo ważny plik:, proszę czekać.

Rysunek 5 . 1 7 . Element progres s w przeglądarce Chrome

Pobierasz bardzo ważny plik, proszę czekać .

ukończono 45%
Rysunek 5 . 18. Awaryjne wyświetlanie w przeglądarkach nieobsługujących elementu progres s

Chodzi tu o to, że zielony (lub niebieski - w zależności od systemu operacyjnego i wersji przeglądarki)
pasek lub opis tekstowy powinny być aktualizowane na bieżąco, tak aby użytkownik wiedział, j aka część
pracy została ukończona, a jaka pozostaje do wykonania.

Tabela 5 . 13. Obsługa elementu progres s w przeglądarkach i urządzeniach

Android *

Chrome 13+
Fi refox 9 . 0+
Internet Explorer *

iOS Safari *

Opera 11.0+
Safari *

* brak stylizowanego paska. ale zamiast niego


pojawia się awaryjnie tekst

PRZEPIS DLA POCZĄTKUJĄCYCH


Wskazywanie wyniku pomiaru
za pomocą elementu meter
Element meter jest używany do wyświetlania pomiaru, takiego jak temperatura, albo wartości ułamkowej .
Ma on sześć możliwych do użycia atrybutów:
• mi n - minimalna dozwolona wartość. Jeśli nie ma atrybutu mi n, wartością j est zero. Jeśli chcesz,
możesz używać liczb ujemnych.
• max - maksymalna dozwolona wartość.
• va l ue - rzeczywista wartość, która musi być wpisana w przypadku użycia elementu meter.
• l ow - dolna część zakresu wartości.
• h i gh - górna część zakresu wartości.
• opt imum - wartość optymalna. Ta wartość musi się mieścić między mi n i max. Może być jednak
większa niż wartość atrybutu h i gh.

Oto kilka prostych przykładów:


<p>Twoja punktacj a : <met er val ue= " 2 " max= " lO">2 z lO</meter></p>
<p>Twoja punktacj a : <met er val ue= " 9 1 " m i n = " O " max= " lOO" l ow= "40" hi gh= " 90 " opt i mum= " lOO">bardzo
�dobrze</meter></p>
Przechodzenie do elementu form po załadowan i u strony 123

Element meter (pokazany na rysunku 5 . 1 9) może być także użyty do wskazania ilości miejsca na dysku
twardym:
<meter m i n= " O " val ue= " 5 1 2 " max=" 1024" >Używasz doktadn i e 50% poj emności Twoj ego twardego dysku<jmeter>

Twoja punktacja:
Rysunek 5 . 19. Element meter w przeglądarce Chrome

Tabela 5 . 14. Obsługa elementu meter w przeglądarkach i urządzeniach

Android *

Chrome 13+
Fi refox *

Internet Explorer *

iOS Safari *

Opera 11.0+
Safari *

* brak stylizowanego paska. ale zamiast niego


pojawia się awaryjnie tekst

PRZEPIS DLA POCZĄTKUJĄCYCH


Przechodzenie do elementu form po załadowaniu strony
Atrybut autofocus daje szansę uaktywnienia klawiatury na kontrolce formularza po załadowaniu strony. Jeśli
byłaby ona uaktywniona na elemencie ; nput albo textarea, użytkownik mógłby rozpocząć pisanie, kiedy tylko
strona zostałaby załadowana. Jeśli pole, na którym klawiatura zostanie automatycznie uaktywniona, posiada
tekst zastępczy, nie będzie on widoczny, ponieważ w polu będzie się znajdował kursor tekstowy. Dawniej
do wykonania tego zadania trzeba było użyć J avaScriptu. Atrybut autofocus jest atrybutem logicznym, więc
albo jest włączony, albo wyłączony. Rysunek 5.20 pokazuje przykład.

Przeszukaj witrynę jI
I Szukaj I
Rysunek 5 20. Atrybut autofocus użyty na polu tekstowym w przeglądarce Safari

Tabela 5 . 15 . Obsługa atrybutu autofocus w przeglądarkach i urządzeniach

Android
Chrome 9 . 0+
Fi refox 4.0+
Internet Explorer
iOS Safari
Opera 10.0+
Safari 5 . 0+
124 Rozdział 5. Form u l a rze internetowe HTML5

Atrybut autofocus należy stosować ostrożnie. Automatyczne przeskakiwanie lub przewijanie strony do
jakiegoś miejsca nie jest najlepszą praktyką przy projektowaniu stron, ponieważ użytkownicy wykorzystujący
urządzenia przenośne, czytniki ekranu albo mający niską rozdzielczość ekranu nie zobaczą być może istotnych
treści powyżej tego miejsca. Zaleca się, aby autofocus był używany na stronach, w których element formularza
stanowi główną treść, takich j ak strona kontaktowa albo, co pokazano na rysunku 5.20, strona wyszukiwarki
G ak formularz na Google.com, który posiada autofocus, lecz wykonany za pomocą J avaScriptu). Listing 5 . 1 0
pokazuj e kod użyty d o utworzenia rysunku 5.20.

Listing 5 . 10 . Atrybut autofocus n a typie wejściowym search

<l abe l for= " autofocus " >Przeszukaj w i t rynę</l abe l >
< i nput autofocus requ i red type=" searc h " i d= " autofocus" name = " autofocus " />

I
Uwaga
Atrybut autofocus powin ien być użyty na stronie tylko raz. Co ciekawe, jeś l i m asz k i l ka pól na stronie,
wszystkie z atrybutem autofocus, O pera, Chrome i Safari u m ieszczają kursor w ostatn im polu, a Firefox
w pierwszym .

PRZEPIS DLA POCZĄTKUJĄCYCH


Zezwalanie na wiele wartości
Atrybut mu l t ; P l e pozwala użytkownikom wprowadzić więcej niż jedną wartość w danym polu. Można
go stosować w przypadku każdego rodzaju elementu ; nput, więc może być użyty do utworzenia formularza
"Wyślij do przyjaciela" albo aplikacji e-mail pozwalaj ącej użytkownikowi na wprowadzenie wielu adresów
e-mail w polach Do, DW i UDW.

Tabela 5 . 16 . Obsługa atrybutu multiple w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox 4.0+
Internet Explorer
iOS Safari
Opera 11.0+
Safari 5 . 0+

Kolejnym przykładem może być przesyłanie wielu plików. Dawniej wymagałoby to JavaScriptu lub kodu
po stronie serwera, wykrywaj ącego, że plik został wybrany, a następnie wyświetlającego kolejną pozycję
pozwalającą wysłać plik. Teraz, przy użyciu kodu z listingu 5 . 1 1 , możesz to zrobić w HTML5.

Listing 5 . 11 . Zezwalanie na przesyłanie wielu plików

<l abel for= "up1 oad" >Prześ 1 i j p1 i k i </l abe 1 >


< i nput mul t i p1 e type= " fi 1 e" i d= " up1 oad" name= " up1 oad" />
Prosta walidacj a przy użyciu atrybutu requ i red 125

Firefox, Opera, Safari i Chrome obsługuj ą j uż ten nowy atrybut. Safari i Chrome wyświetlaj ą liczbę
wybranych plików. W przykładzie pokazanym na rysunku 5.21 zostały wybrane trzy pliki. Firefox nie
wyświetla automatycznie, ile plików zostało wybranych; zamiast tego pokazuje pełne ścieżki plików wewnątrz
pola tekstowego, rozdzielaj ąc j e przecinkami, na przykład: "C:\plikl .doc, C:\plik2.pdf'. Opera również
pokazuje pełne ścieżki, przy czym umieszcza każdą z nich w cudzysłowach i rozdziela je średnikami.

Prześlij pliki I Wybierz p l ik I " 3 fi le s


I Prześlij l
Rysunek 5 21. Atrybut multiple użyty na polu input typu file

PRZEPIS DLA POCZĄTKUJĄCYCH


Prosta walidacja przy użyciu atrybutu required
Atrybut requ i red j est wykorzystywany w wielu przykładach w tym rozdziale. Jeśli atrybut został użyty,
przeglądarka nie będzie próbowała wysyłać formularza, kiedy wymagane pola będą puste. W przeglądarkach,
które obsługuj ą ten atrybut, jeśli wymagane pola są puste, wyświetli się komunikat błędu, j ak pokazano
na rysunku 5.22.

� To pole Jest wymagane

Rysunek 5 22. Komunikat błędu dotyczący niewypełnionego wymaganego pola w Operze 11

Tabela 5 . 1 7 . Obsługa atrybutu required w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox 4.0+
Internet Explorer
iOS Safari
Opera 11.0+
Safari 5 . 0+

I
Wskazówka
O prócz atrybutu requ i red możesz równ ież dodać ari a-requ i red= " true " , co przyczyn i s i ę do poprawy
dostępności elementów form u larza. Może zainteresuje Cię informacj a , że WordPress rob i tak domyś lnie
w formu larzach komentarzy.
< i nput ari a-requ i red=" true" requi red type= " text " i d= " name" name=" name" />
126 Rozdział 5. Form u l a rze internetowe HTML5

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Pisanie własnych reguł walidacji
Mimo że HTML5 ma wbudowaną walidację, możesz ustanowić własne reguły, wykorzystując atrybut pattern
i wyrażenia regularne.

Tabela 5 . 18. Obsługa atrybutu pattern w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox 4.0+
Internet Explorer
iOS Safari
Opera 11.0+
Safari

Widziałeś na przykład, że typ wejściowy url akceptuje różne rodzaje adresów URL. Możesz temu zapobiec
i zmusić użytkownika do wpisania adresu rozpoczynaj ącego się od http://. Możesz to zrobić za pomocą
własnego wyrażenia regularnego .
Wyrażenie regularne wykorzystane na listingu 5 . 1 2 waliduje tylko adresy URL zaczynające się od http://
albo https://. Akceptuje również subdomeny i łańcuchy zapytań, więc http://ty.witryna.pl/search.aspx?=test
jest prawidłową zawartością, ale mailto:adres nie jest.

Listing 5 . 12 . Atrybut pattern z wyrażeniem regularnym

< i nput requ i red pattern= " (http l https) : // ( [\w-J +\ . ) + [\w-J + (j [\w- . / ?%&=J *) ? " type= " text " i d= " url " name= " url " />

Wyrażenia regularne mogą być zawiłe i szybko się komplikują, więc najlepiej, jeśli będziesz stosować
jak najprostsze wyrażenia. A jeśli utknąłeś na jakimś problemie, skorzystaj z niezliczonych podpowiedzi
zamieszczanych w internecie.

PRZEPIS DLA POCZĄTKUJĄCYCH


Nakładanie ograniczeń na wprowadzane dane
Wpisy w formularzu HTML5 mogą być ograniczane, jeśli jest taka potrzeba, poprzez różne atrybuty.

step
Atrybut step może być stosowany w przypadku typów wejściowych number, range i t ime. Określa on przyrosty
wartości liczb, które dany element i nput może przyjąć. Spójrz dla przykładu na znajdujący się wcześniej w tym
rozdziale listing 5.6 (dotyczący typu wej ściowego n umber).
Nakładanie ograniczeń n a wprowadzane dane 127

Tabela 5 . 19. Obsługa atrybutu step w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox
Internet Explorer
iOS Safari
Opera 11.0+
Safari 5.1

min i max
Użyte n a elemencie meter i n a typach wejściowych n umber oraz range atrybuty mi n i max ustalają dozwolony
zakres wartości elementu. Nie są to wymagane atrybuty i możesz użyć albo mi n, albo max, albo ich obu.
Ustalają one reguły walidacji i jeśli wartość elementu nie będzie się mieścić w zakresie od mi n do max, formularz
nie zostanie wysłany. Mogą być użyte na typach wej ściowych date i t i me do nałożenia ograniczenia na
wartość wybieraną przez użytkownika, na przykład w przypadku kalendarza wydarzeń lub wyszukiwania
pomiędzy datami.

Tabela 5 . 20. Obsługa atrybutów min i max w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox
Internet Explorer
iOS Safari
Opera 11.0+
Safari 5.1

formnov alid ate i nov alid ate


Jeśli masz formularz, przy którym nie chcesz korzystać z walidacji dokonywanej przez przeglądarkę, możesz
użyć atrybutu formnova l i date albo nova l i date. Użycie jednego z nich jest szczególnie przydatne, jeśli chcesz
zapisać aktualny stan formularza, zamiast wysyłać go, kiedy jest na przykład duży lub jeśli na obecnym etapie
procesu nie jesteś zainteresowany walidowaniem danych, ponieważ użytkownik ma inne etapy do dokończenia
przed ostatecznym wysłaniem danych.

Tabela 5 21. Obsługa atrybutów formnovalidate i novalidate w przeglądarkach i urządzeniach

Android
Chrome 10.0+
Fi refox 5 . 0+
Internet Explorer
iOS Safari
Opera 11.0+
Safari
128 Rozdział 5. Form u l a rze internetowe HTML5

Możesz umieścić noval i date w elemencie formularza, dzięki czemu w momencie wysyłania go wszelkie
nieprawidłowe formaty lub puste pola zostaną zignorowane:
<form noval i date>

Możesz również umieścić atrybut formnova l i date w każdym poszczególnym elemencie formularza.
Na przykład mógłbyś umieścić go przy typie wejściowym url , a przeglądarka będzie ignorować walidację
tego elementu (choć wątpliwe j est, żebyś chciał podjąć wysiłek używania typu wejściowego url , a następnie
ignorował walidację).

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Dostosowywanie formularza i nadawanie mu styl u
Jedną z pierwszych rzeczy, o którą pytaj ą proj ektanci, j est możliwość nadawania stylu nowym elementom.
Cóż, w pewnym sensie można to zrobić. Chociaż nie można zmienić wyglądu pola wyboru daty, ponieważ
jest ono generowane przez przeglądarkę (w przyszłości może się to zmienić), możesz nadawać styl pojemnikom
elementów i np ut, na przykład krawędziom, czcionkom i kolorowi tła, ponieważ w CSS3 istnieją sposoby,
których możesz użyć do nadania polom różnych stylów w zależności od ich stanu.
Kod z listingu 5 . 1 3 generuj e widok z rysunku 5.23; zauważ różne stany pól. Pola początkowo
są pomarańczowe i pokazują ikonkę gwiazdki, gdyż są wymagane.

Listing 5 . 13 . Nowe możliwości CSS3 dotyczące elementu form

<styl e>
* {marg i n : O; fon t : 13px tahoma, verdana , sans-ser i f ; padd i ng : O ; }
form { paddi ng-top : lOpx; w i d t h : 3 1Opx ; }
l i { e l ear: bot h ; l i st -styl e-type : non e ; marg i n : O O 10px ; }
l abel { d i spl ay : b l oe k ; fl oat : l eft ; marg i n : O lOpx O O ; padd i n g : 5px ; text-al i gn : ri ght ; w i d t h : 100px}
i nput { bae kground-pos i t i on : 3px 5px ; bae kground-repeat : no-repeat ; border-rad i u s : 5px ; padd i n g : 5px 5px 5px
�25px; w i d t h : 155px ; }
i nput : foeus { out l i ne : none ; }
i nput : i nval i d : requi red { bae kground- image : url (aster i s k . png) ; box-s hadow: Opx Opx 5px #fObbI 8 ; border: 2px
�sol i d #fObb I8 ; }
i nput : focus : i nval i d { bae kground- image : url ( i nval i d . png) ; box-s hadow: Opx Opx 5px #bO I 2 1 2 ; border:
�2px sol i d #bO I 2 1 2 ; }
i nput : val i d { bae kground- i mage : url (aeeept . png) ; border: 2px sol i d #7ab526 ; }
i nput [type=submi t] {baekgroun d : #7ab5 2 6 ; border: none; box-s hadow: Opx Opx 5px #7ab526 ; eol or: #fff;
�eursor: poi nter; fl oat : r i g h t ; font-we i gh t : bol d ; padd i ng-l eft : 5px; w i dt h : auto ; }
</styl e>
<form>
<ol >
<l i ><l abel for= " tel ">Tel . : </l abe l >< i nput pl aeehol der= " np : 0 12345 " requ i red type= " tel " i d= " tel "
�name= " tel " /></l i >
<l i ><l abel for="webs i te " >W i tryna : </l abel ><i nput requi red type= " url " i d= " webs i te" name= " webs i te " /></l i >
< l i ><1 abel for=" ema i l . . >E-ma i l : </1 abel ><i nput requ i red type= " emai l " i d = " emai l " name= " ema i l " /></1 i >
< l i >< i nput type= "subm i t " val ue= " Wyśl i j formul arz " /></1 i >
</0 1 >
</form>
Kom u n i katy błędów 129

Tel.: [O 0 1 23456 789

Witryn a : [O bla-bla.pll
E-mail : [*
�____________-J

Rysunek 5 . 23. CSS3 użyty do odwołania się do różnych stanów pól formularza

Gdy pole jest aktywne, ale jest puste lub zawiera nieprawidłową treść, pojemnik j est czerwony i pokazuje
ikonkę wskazującą błąd. Jeśli zawartość j est poprawna, przeglądarka zmienia kolor pola na zielony i ikonkę
na symbol fajki. A wszystko to bez JavaScriptu!
Nie omówimy wszystkich nowych możliwości CSS-a, ale na rysunku 5.23 użyliśmy następuj ących
pseudoklas z CSS3 Basic User Interface Module! (http://www.w3.orglTR/css3-ui):
• : va l ; d - element formularza otrzymuje tę klasę, gdy jego zawartość jest zgodna z typem
elementu i przeszła walidację.
• : ; nva l ; d - jeśli element formularza ma niepoprawną zawartość, stosowana jest ta klasa.
• : req u ; red - do tej klasy będzie przypisany każdy element formularza, który ma ten atrybut.

Komunikaty błędów
Mamy nadzieję, że śledziłeś poprzednie przykłady i zauważyłeś, iż komunikaty o błędach są wyświetlane
inaczej w Operze i Firefoksie. Opera wyświetla drgaj ący, różowy komunikat o błędzie, podczas gdy Firefox
pokazuj e spokojniejszy, nieruchomy komunikat. Obecnie nie można zmienić sposobu, w jaki te komunikaty
są wyświetlane, ponieważ są generowane przez przeglądarkę. W przyszłości może się to zmienić, ponieważ
obecnie ma miejsce dyskusja, czy umożliwić ich edycję za pomocą CSS-a. A zatem chociaż możesz narzekać,
że komunikaty o błędach wyglądają okropnie lub nie spełniają firmowych wytycznych odnośnie do wyglądu
stron, będą one w danej przeglądarce spójne, co z pewnością oznacza zwycięstwo użyteczności, gdyż
użytkownicy oczekują j ednego stylu błędów podczas korzystania z wybranej przez siebie przeglądarki.
Chociaż nie możesz jeszcze zmienić stylu błędów, możesz zmodyfikować tekst komunikatu błędu przy
użyciu JavaScriptu i metody setCustomVa l ; d ; ty ( ) . Listing 5 . 1 4 zawiera przykład, w którym domyślny
komunikat o błędzie zostaje nadpisany, gdy zostaną dodane nieprawidłowe treści.

Listing 5 . 14. Formularz rejestracji w HTML5

<form>
<l abel for= " emai l ">E-mai l <jl abel >
< i nput oni nput="check O " type = " emai l " i d = " emai l " name= " emai l " j>
< i nput type= " subm i t "j>
<jform>
<scri pt>
funct i on chec k ( ) (
var emai l Input = document . getEl ementBy l d ( " emai l " ) ;
ema i l I nput . s et C u s tomVal i d i ty ( " Btąd. Proszę popraw i ć . " ) ;

<jscri pt>

! Podstawowy moduł interfej su użytkownika - przyp. tłum.


130 Rozdział 5. Form u l a rze internetowe HTML5

PRZEPIS DLA ZAAWANSOWANYCH


Wszystko razem - tworzenie formularza rejestracyjnego
Teraz, kiedy poznałeś wszystkie nowe funkcje formularzy HTML5, wykorzystajmy kilka z nich, dodając
odrobinę CSS-a, by zobaczyć, w jaki sposób nowe funkcje umożliwią tworzenie formularza rejestracyjnego.
HTML i CSS z listingu 5 . 1 5 dają wynik zaprezentowany na rysunku 5.24.

Listing 5 . 15 . Solidny formularz rejestracyjny

< ! DOCTYPE html >


<html l ang= " p l ">
<head>
<meta eharset = " utf-8">
< t i t l e>Duży formul arz rej estraeyj ny</t i t l e>
<styl e>
* {marg i n : O; padd i ng : O ; }
body { ba e kground : #fff; eol or: #000 ; font : normal 62 . 5%/1 . 5 tahoma , verdana , sans-seri f; }
hl { font - s i z e : 2 . gem; font-we i ght : bol d ; marg i n : lem O lem 10px ; }
form { padd i ng : O lOpx ; w i dt h : 700px ; }
l egend { l eft : -9999px ; pos i t i on : absol ut e ; }
f i e l dset { border: lpx sol i d #eee ; border-rad i u s : 5px ; fl oat : l eft ; padd i ng : 10px; w i d t h : 320px ; }
f i e l dset : nth-of-type ( 1 ) {marg i n-ri ght : 10px ; }
l i { e l ear: bot h ; l i st -styl e-type : non e ; marg i n : O O 10px ; }
l abel , i nput { font- s i z e : 1 . 3em ; }
l abel { d i spl ay : b l oe k ; padd i n g : O O 5px; w i d t h : 200px}
i nput { bae kground-pos i t i on : 295px 5px ; bae kground-repea t : no-repeat ; border: 2px sol i d #eee ; border-radi u s :
�5px ; padd i ng : 5 p x 25px 5 p x 5 p x ; w i d t h : 285px ; }
i nput : foeus { out l i ne : none ; }
i nput : i nval i d : requi red { bae kground- image : url (aster i s k . png) ; box-shadow: none ; }
i nput : foeus : i nval i d ( bae kground- i mage : url ( i nval i d . png) ; box-s hadow: Opx Opx 5px #b0 1 2 l 2 ; border: 2px
�sol id #b012 l2 ; }
i nput : val i d : requ i red { bae kground- image : url (aeeept . png) ; border: 2px sol i d #7ab52 6 ; }
i nput [type=dateJ , i nput [type=numberJ { bae kground-pos i t i on : 275px 5px ; text-al i gn : l eft ; }
i nput [type=eol orJ , i nput [type=rangeJ { paddi ng-ri ght : 5px ; }
i nput [type=rangeJ : before { eontent : " l " ; }
i nput [type=rangeJ : afte r { e ontent : " lO" ; }
d i v#range l abel { font-we i gh t : bol d ; }
output { font-s i z e : 1 . 3em ; font-we i ght : bol d ; d i sp l ay : b l oe k ; text -al i gn : center ; }
d i v { e l ear: bot h ; fl oat : l eft ; marg i n : lOpx O ; text -al i gn : center; w i d t h : 100% ; }
d i v l abel { w i d t h : 100% ; }
i nput [type=submi tJ {baekgroun d : #7ab5 2 6 ; border: none; box-s hadow: Opx Opx 5px #7ab526 ; eol or: #fff;
�eursor: poi nter; font - s i z e : 3em; font-we i ght : bol d ; marg i n : 20px auto ; padd i ng : l5px ; w i d t h : auto ; }
i nput [type=submi tJ : hover { box-shadow: Opx Opx 25px #7ab526 ; }
</styl e>
</head>
<body>
<hl>Zap i s z s i ę , by otrzymać nasz ni ezwykty produkt . </hl>
<form>
<fi el dset>
<l egend>Dane osobowe</l egend>
<ol >
< l i ><l abel for= " name " > I m i ę i nazwi s ko</l abe l >< i nput autofoeus requ i red type= " tel " i d= " name"
4-name = " name" /></l i >
< l i ><l abel for= " b i rthday">Data urodzen i a</l ab e l ><i nput req u i red type= " date" i d= " b i rt hday"
�name= " b i rt hday" /></l i >
<l i ><1 abe l for= " webs i t e " >W i t ryna</l abe l >< i nput p l aeeho l der="htt p : //moj awi t ryna . p l " pattern=
�" ( htt p l https ) : // ( [ \w-J +\ . ) + [\w-J + (j [\w- . /?%&=J * ) ? " type= " url " i d= " webs i te" name = " webs i te" /></l i >
Wszystko razem - tworzenie form u larza rejestracyjnego 131

<l i ><l abel for= " j ob " > Praca</l abe l >< i nput l i st = " j obl i s t " requi red type = " tex t " i d= " j o b " name= " j ob" />
<datal i st i d = " j obl i s t " >
<opt i on l abe 1 = " kosmi czny kowa l " v a l u e = " kosm i czny kowa l " >
<opt i on l abel = "mi ędzynarodowy pl ayboy" val ue= "mi ędzynarodowy pl ayboy" >
<opt i on l abel = " twórca stron i nternetowych" val ue=" twórca stron i nternetowyc h " >
<opt i on l abel = " proj e ktant stron i nternetowyc h " val ue= " proj ektant stron i nternetowyc h " >
<opt i on l abel = " złota rąc z ka" val ue = " z łota rączka">
</datal i st>
c/l i >
< l i ><l abe l for= " s a l ary">Przyb l i żone roczne dochody</l abe l ><i nput p l aceho l der= " z ł " requ i red m i no" O "
'-+--s tep= " IOOQ" type=" number" i d = " sal ary" name= "sal ary" /></l i >
</01 >
</f i e l dset>
<fi el dset>
<l egend>Dane konta ktowe</l egend>
<ol >
<l i ><l abel for= " addres s 1 ">Pi erwszy wi ersz adresu</l abel ><i nput req u i red type= " text" i d= " addres s 1 "
�name = " addres s 1 " /></l i >
<l i ><l abel for= " addres s 2 ">Drugi wi ersz adresu</l abe l >< i nput requ i red type= " text" i d = " address 2 "
�name = " address 2 " /></l i >
<l i ><l abel for= " country">Kraj</l abe l >< i nput l i st = " countryl i st " req u i red type= " text " i d= " country"
4-name = " country" />
<datal i st i d = " countryl i st " >
<opt i on l abel = " Po l s ka" val ue= " Pol s ka " >
<opt i on l abel = " W i el ka Brytan i a" val ue= " W i e l ka Brytan i a" >
<opt i on l abel = " USA" val ue= " USA">
</datal i st>
c/l i >
< l i ><l abel for= " tel " >Tel . </l abe l >< i nput pl acehol der= " np . 0 12345" type= "tel " i d= " tel "
�name= " t e l " /></1 i >
<l i ><l abel for= " emai l ">E-mai l </l abel ><i nput requ i red type= " emai l " i d= " ema i l " name= " ema i l " /></l i >
</01 >
</f i e l dset>
<d i v i d= " personal i se" >
<h2>Dostosuj swój profi l </h2>
<l abel for= "col or">Wybi erz kol or strony</ l abel >
<i nput type=" col or" i d= " col or" name= " col or" />
</d i v>
<d i v i d= " range ">
<l abel for= " exc i ted">Jak w s kal i od 1 do 10 podobaj ą C i się formu l arze HTML5?</l abe l >
<i nput m i n= " l " max= " IO" step= " O . 5 " type= II range" i d= " exci ted" name= " ex c i ted" />
<output onformi nput= " val ue=exc i ted . val ue + ' / 10 "' ></output>
<i nput type= " submi t " val ue = " Zap i sz s i ę " />
</d i v>
c/form>
c/body>
</html >

Na rysunku 5.24 użyliśmy typów wejściowych te ' , date, ur' , number, text, ema ; ' , co, or i range. W kilku
przypadkach został także użyty element data' ; st, choć mogliśmy alternatywnie wykorzystać element se' ect,
ale w tym formularzu chcemy, aby ludzie wpisywali swoje stanowiska lub kraj e z klawiatury. Pod koniec
formularza został wykorzystany typ wejściowy range i wraz z nim element output. Wszystkie elementy
zawierające dane, które musimy uzyskać, mają atrybut requ ; red, który uruchomi wbudowany w przeglądarkę
mechanizm walidacji. Aby formularz wyglądał trochę ładniej, dodaliśmy kilka nowych selektorów CSS3,
dzięki czemu pokazaliśmy stany wymagany, prawidłowy i nieprawidłowy elementów.
132 Rozdział 5. Form u l a rze internetowe HTML5

Zapisz się, by otrzymać nasz niezwykły produkt.

Imię i ni!lzwisko Pierwszy wiersz adresu

* II ol
Data urodzenia Drugi wiersz adresu

12010.12.17 0 ::1 _____


*J
Witryni!ll Kri!!lj

nttpJ/mO)a_
Wl-"
ry_
na-'pI______
Pra ca Tel.

I kosmiczny kowal
ol np 012345
J
przyblizone roczne dochody
*8
E-mail

zl @

Dostosuj swój profil


Wybierz kolor strony

f"iiiiii .:J
Jak w skai od 1 do 10 podoba� Ci się fonnuiarze łlTMLS?
D
5.5 / 10

Zapisz się

Rysunek 5 . 24. Formularz rejestracyjny w Operze 11 utworzony przy użyciu kilku elementów HTML 4,
nowych elementów formularzy HTML5 oraz CSS-a

Podsumowanie
W tym rozdziale poznałeś wszystkie nowe typy wejściowe i dowiedziałeś się, j ak działaj ą w przeglądarkach,
jeśli są w pełni obsługiwane. Te nowe typy wej ściowe wraz z wbudowaną natywną walidacj ą i nowymi
funkcjami CSS w dobrych przeglądarkach bardzo ułatwiaj ą tworzenie formularzy i przekazywanie
użytkownikowi informacji zwrotnych. Chociaż czeka nas jeszcze długa droga, zanim wszystkie przeglądarki
nadrobią zaległości, pewnego dnia - im szybciej , tym lepiej - nie będziesz musiał polegać na JavaScripcie,
by tworzyć użyteczne funkcje, takie jak kontrolki wyboru daty, suwaki czy walidacja. Wszystkie nowe typy
wejściowe, takie jak ema ; l , te l i date, działają w przeglądarkach choćby poprzez pokazywanie pola tekstowego,
więc nie ma żadnego usprawiedliwienia, by nie zacząć z nich korzystać.
6
Rysowanie na płótnie

W
2004 roku firma Apple opracowała na potrzeby aplikacji Dashboard systemu operacyjnego Mac OS X
element o nazwie canvas (płótno), przeznaczony do rysowania, który został później zaimplementowany
w przeglądarce Safari. Specyfikacja HTMLS wykorzystała ten element i powiązany z nim zestaw interfejsów
API, aby zapewnić realizację podstawowych funkcji rysowania - przed wprowadzeniem elementu c a nv a s
przeglądarki wymagały do tego celu zastosowania zewnętrznej wtyczki. Ponieważ c a n v a s jest mechanizmem
rysowania bazuj ącym na pikselach, a nie wektorach czy warstwach, podstawowa funkcjonalność jest dość
prymitywna, ale jak dowiesz się w tym rozdziale, i tak umożliwia tworzenie zaawansowanych obrazów.

Element canvas - informacje ogólne


Implementacj a płótna opiera się na dwóch filarach: elemencie c a n v a s w HTML-u oraz JavaScripcie
umożliwiającym wykonywanie na nim operacji. W przypadku malowania obrazu płótno jest puste, dopóki
malarz nie użyje pędzli, narzędzi i innych środków do stworzenia za ich pomocą dzieła sztuki. W J avaScripcie
w podobnym celu używane są narzędzia kształtu, efekty oraz przekształcenia - ich wykorzystanie skutkuje
uaktualnionym widokiem nowych pikseli na płótnie.
Element c a n v a s udostępnia pustą powierzchnię, której możesz użyć do dynamicznego renderowania
grafiki, obrazków i tekstu. Funkcjonalność płótna j est ogromna, więc najpierw omówimy podstawy, takie
jak rysowanie kształtów, a następnie przejdziemy do niektórych bardziej skomplikowanych efektów
i przekształceń. A jeśli podoba Ci się tego typu zabawa, zachęcamy do samodzielnego poszerzania wiedzy,
ponieważ w tej książce tak naprawdę zaledwie dotknęliśmy tematu. Przed rozpoczęciem intensywnego
użytkowania płótna zajmijmy się jednak podstawami.
Tabela 6 . 1 pokazuje, które wersje najpopularniejszych przeglądarek obsługują element c a n va s .

Tabela 6 . 1 . Dostępność elementu canvas w przeglądarkach

Android 2 . 1+
Chrome 10.0+
Fi refox 3 . 6+
Internet Explorer 9 . 0+
iOS Safari 3 . 2+
Opera 10.6+
Safari 3 . 2+
134 Rozdział 6 . Rysowanie na płótn ie

I
Uwaga
W przypadku Internet Explorera S i starszych wersj i będziesz potrzebował zewnętrznej bibl iote k i .
Możesz w tym ce lu użyć b i b l i oteki explorercanvas ( http://code.google. com/p/explorercanvas) .
J ako że potrzebujesz jej tyl ko w przypadku I ES, możesz um ieścić ją w poleceniu warun kowym :
< ! - - [i f l te JE 8] ><scri pt src= " excanvas . j s "></scri pt>< ! [end i f] - ->

P oczątki
Listing 6.1 zawiera kod potrzebny do uruchomienia elementu canvas. Jeśli wypróbujesz ten kod w przeglądarce,
na płótnie nie zobaczysz niczego, ponieważ nie został dodany służący do rysowania na nim kod javascriptowy.
Prostym sposobem sprawdzenia, czy płótno zostało wyświetlone przez przeglądarkę, jest dodanie obramowania
elementu przy użyciu CSS-a.

Listing 6 . 1 . Konfigurowanie elementu canvas

< ! DOCTYPE html >


<html >
<head>
<styl e>
canvas {
border: lpx sol i d #000 ;
}
c/styl e>
</head>
<body>
<canvas i d= "myCanvas" width="640" height= "480"></canvas>
c/body>
</html >

Element canvas ma standardowe atrybuty elementu HTML - nawet w skrajnym przypadku będziesz
potrzebował atrybutów i d, wi dth i h e i ght, aby ustawić rozmiar płótna i móc odwoływać się do niego z poziomu
JavaScriptu. Ponadto elementowi canvas można, j ak każdemu innemu elementowi, nadać styl za pomocą
CSS-a. Na listingu 6 . 1 zostało zastosowane obramowanie, dzięki któremu możesz szybko sprawdzić, czy
płótno zostało umieszczone na stronie i czy ma poprawne wymiary. Mógłbyś również dodać kolor lub styl
dla tła - zostaną one wyświetlone, ponieważ płótno jest domyślnie przezroczyste i nie przesłania niczego,
co się pod nim znaj duje. Właściwość tę można wykorzystać, nakładając płótno na inne elementy HTML,
tak aby można było następnie na nich rysować.
Zauważ, że na listingu 6 . 1 uwzględniliśmy w kodzie znacznik zamykający </canvas>. Wynika to z chęci
udostępnienia pojemnika na treść zastępczą i dostosowania kodu do wymagań przeglądarki Firefox, która
oczekuje treści zastępczej . Dzięki pojemnikowi na treść zastępczą przeglądarka może wyświetlić zawartość
znajduj ącą się pomiędzy znacznikami, jeśli element canvas nie j est przez nią obsługiwany. Podobnie j ak
w przypadku innych elementów, w celu wyświetlenia treści tekstowej mógłbyś tu umieścić atrybut a l t,
ale jeśli znacznik canvas nie jest obsługiwany, zostanie wyświetlona dowolna treść znajdująca się między
otwieraj ącym i zamykającym znacznikiem elementu, co może być przydatne do wyświetlania rozmaitych
elementów, od tekstu do obrazków.
Aby rysować na płótnie z poziomu JavaScriptu, najpierw musisz odwołać się do kontekstu płótna,
na którym chcesz rysować. W tym celu Twój skrypt wyszuka za pomocą identyfikatora element canvas,
a następnie użyje metody getContext, aby odwołać się do dwuwymiarowego kontekstu tego elementu.
Nałożenie siatki na płótno 135

To ostatnie odwołanie tworzy powiązanie przydatne później do zmieniania pikseli płótna, jak pokazano
poniżej :
<canvas i d= "mycanvas" wi dth= " 640" hei ght= "480" ><jcanvas>
<scri pt>
var canvas = document . getEl ementByl d ( ' mycanvas ' ) . getContext ( ' 2d ' ) ;
<jscri pt>

Metodę getContext można również wykorzystać do weryfIkacji skryptu i ustalenia, czy dana przeglądarka
obsługuje rysowanie na płótnie. Zwykłe sprawdzenie z użyciem elementu eanvas zwróci wartość t rue, jeśli
jest on obsługiwany, lub wartość fa 1 se w przeciwnym wypadku:
var canvas = document . getEl ementByl d ( 'mYcanvas ' ) ;
i f (canvas. getContext) {
II element canvasjest obsługiwany

Zależnie od tego, czy przeglądarka obsługuj e funkcjonalność płótna, J avaScript mógłby następnie
rozpocząć realizację odpowiednich procedur.

Współrzęd ne x i y
Ostatnim obszarem, który musimy omówić przed przejściem do podstawowych narzędzi rysujących płótna,
jest wykorzystywany do rysowania na płótnie układ współrzędnych. Jako że płótno bazuje na pikselach,
układ współrzędnych (x, y) jest używany do określania konkretnej pozycji albo aktualizowanego piksela.
Współrzędne te są również wykorzystywane przez różne narzędzia do oznaczania punktów początkowych
i końcowych oraz innych pozycji. Jeśli używałeś programów takich jak Adobe Photoshop, korzystanie
z systemu rysowania opartego o współrzędne nie będzie dla Ciebie niczym nowym. Kluczową sprawą
jest to, że w tym układzie współrzędnych punkt (O, O) znajduje się domyślnie w lewym górnym rogu płótna,
przy wartości x rosnącej przy przesuwaniu się w prawo oraz wartości y rosnącej przy przesuwaniu się w dół.
W ramach wprowadzenia do niektórych narzędzi rysujących i systemu siatki płótna w pierwszym przepisie
w tym rozdziale nałożymy siatkę na płótno, tak abyś ją zobaczył.

PRZEPIS DLA POCZĄTKUJĄCYCH


Nałożenie siatki na płótno
Podstawą rysowania za pomocą elementu eanvas jest system siatki. Element eanvas korzysta z systemu siatki
w przypadku używania podstawowych narzędzi do rysowania kształtów, stosowania efektów i wykonywania
przekształceń, jednak przy wszelkich działaniach na płótnie siatka pozostaje niewidoczna. W tym przepisie
użyjesz dwóch podstawowych metod rysuj ących kształty, 1 i ne i a re , aby utworzyć na płótnie siatkę z małymi
punktami na przecięciach pionowych i poziomych linii. Zobaczysz, j akie znaczenie ma system siatki dla
podstawowych narzędzi rysujących.
Płótno zdefiniujemy jako element maj ący 600 pikseli szerokości i 400 pikseli wysokości. Co 100 pikseli
będą rysowane pionowe i poziome linie tworzące siatkę. Aby pokazać współrzędne przecięć, wykorzystamy
efekt fi 1 1 Text, który pozwala wyświetlić za pomocą JavaScriptu tekst na elemencie eanvas. Na końcu
powinieneś otrzymać rezultat przedstawiony na rysunku 6 . 1 .
136 Rozdział 6 . Rysowanie na płótn ie

(0.0) (100.0) (200.0) (300.0) (400.0) T500.0) (500.0)

I
....
(0. \ 00) (100 . 1 00) (200.100) (300. 100) (400.100) -(500. 1 00) (6ll0. 1 CoO)

I
(0.2lI0) ( 1 00.200) (200.200) (300.200) (400.200) (500.200) (000.200)

I
(0.300) (1 00.300) (200.300) (300.300) (400.300) (500.300) (6ll0.300)

I
(0.400) (1 00.400) (200.400) (300.400) (400.400) (500.400) (600.400)
...

Rysunek 6 . 1 . System współrzędnych siatki płótna narysowany z wykorzystaniem metod line, arc i fillText

Sprawmy, aby siatka była widoczna, wykonując następujące kroki i wykorzystując listing 6.2:
1. Utwórz pustą stronę HTML z takimi znacznikami wewnątrz znacznika body, jakie przedstawiono
na listingu 6.2, w tym z otwierającym i zamykającym znacznikiem canvas oraz zastępczym tekstem
pomiędzy nimi.
2. Dodaj blok styl e z identyfikatorem płótna jako selektorem.
3. Dodaj wywołanie metody wi ndow . addEventL i stener, aby uruchomić funkcję showGr i d, kiedy strona
zostanie załadowana, oraz deklaracje zmiennych płótna i kontekstu.
4. Dodaj funkcję showG r i d rysującą linie, punkty i tekst na płótnie.

Listing 6 . 2 . Rysowanie siatki płótna

< ! DOCTYPE html >


<html >
<head>
<meta charset = " UT F-8" j>
<t i t l e>6 . 2 . System s i at k i pfótna<jt i t l e>
<styl e>
#canvas {
border: lpx so l i d #03 F ;
background : #CFC;

<jstyl e>
<scri pt>

II deklaracja zmiennych będących odwołaniami do płótna i kontekstu


var canva s ;
v a r context ;
Nałożenie siatki na płótno 137

II nałóż siatkę na płótno


funct i on showGri d () {

IIpobierz odwołania do płótna, a następnie do kontekstu rysowania


canvas = document . get E l ementByld ( ' canvas ' ) ;
context = canvas . getContext ( ' 2d ' ) ;

II ustal grubość kreski i kolor kresek siatki


contex t , l i neW i dt h = 1 ;
context , strokeStyl e = ' #999 ' ;

II ustal odległości między liniami siatki


l i neSpac i ng = 100;

II inicjalizuj pozycje x i y
var xPos O;
var yPos = O ;

II ustal, jaka jest liczba poziomych i pionowych linii n a siatce


var numHori zontal Li nes = parselnt (canvas , he i ghtjl i neSpac i ng) ;
var numVert i cal Li nes = parselnt (canvas , w i dthjl i neSpac i ng) ;

II rysuj linie poziome


for (var i = l ; i <=numHori zontal Li nes ; i ++)
yPos = i *l i neSpac i ng ;
context , moveTo (O ,yPos ) ;
context , l i neTo (canvas , w i dt h ,yPos ) ;
context , stroke ( ) ;

II rysuj linie pionowe


for (var i = l ; i <=numVert i cal L i nes ; i ++)
xPos = i *l i neSpac i ng ;
context , moveTo (xPos , O ) ;
context , l i neTo(xPo s , c anvas , he i ght) ;
context , stroke ( ) ;

II dodaj koła i współrzędne przecięć linii siatki


for (var y=O; y<=numHori zontal Li nes ; y++) {
for (var x=O; x<=numVert i cal L i ne s ; x++) {

II oblicz pozycje x i y
xPos x*l i neSpac i ng ;
yPos = y*l i neSpac i ng ;

Iljeśli znajdujemy się na pozycji (0, 0), należy zmienić kolor


i f (x==O && y==O) {
context , fi l l Styl e= ' #fOO ' ;
el se {
context , fi l l Styl e= ' #OOO ' ;

II narysuj koło w punkcie


context , begi nPat h ( ) ;
II rysuje koło o promieniu 5 w pozycji x i y
context , arc (xPos ,yPos , 5 , 0 , Mat h , PI*2 , true) ;
context , c l osePat h ( ) ;
contexL fi 1 1 ( ) ;

II wyświetl tekst współrzędnych


138 Rozdział 6 . Rysowanie na płótn ie

II sprawdź. czyjesteś na ostatniej linii pionowej. a jeśli tak. wnieść tekstpo lewej stronie
i f (x==numVert i cal Li nes) {
context . t extAl i gn = ' ri ght ' ;
xPos -= 5 ;
el se {
context . textA l i gn ' l eft ' ;
xPos += 5 ;

II sprawdź, czyjesteś na ostatniej linii poziomej, a jeśli tak, umieść tekst powyżej
i f (y==numHori zontal Li nes) {
yPos -= 8 ;
el se {
yPos += 1 2 ;

II dodaj tekst do płótna


context . fi 1 1 Text ( ' ( ' +x*l i neSpac i ng+ ' , ' +y*l i neSpac i ng+ ' ) , ,xPos ,yPos ) ;

IIpo załadowaniu strony narysuj siatkę


wi ndow . addEventLi stener ( ' l oad ' , s howGr i d , fal se) ;

<jscri pt>
<jhead>
<body>
<h 1>System s i at k i ptótna<jh1>
<canvas i d= "canvas" width="600" hei ght="400">
El ement canvas nie jest obsługi wany przez Twoją przegl ądarkę .
Uruchom tę stronę w i nnej przegl ądarce .
</canvas>
<jbody>
<jhtml >

Nie będziemy zbytnio wchodzić tutaj w szczegóły dotyczące samego kreślenia linii, punktów czy tekstu,
ponieważ odpowiednie metody i efekty zostaną opisane w dalszej części rozdziału. Najważniejsze, aby zrozumieć
proces, z którym masz do czynienia, gdy chcesz rysować na płótnie. W tym przepisie utworzyłeś w HTML-u
element canvas, który ma 600 pikseli szerokości i 400 pikseli wysokości. Elementowi temu został nadany styl
zawierający obramowanie i kolor tła. Ponieważ płótno jest domyślnie przezroczyste i rysowane są na nim
tylko linie, punkty i tekst, po załadowaniu strony w przeglądarce obramowanie oraz kolor tła będą widoczne.
Po wczytaniu strony zostanie uruchomiona funkcja s howGri d. Najpierw musisz utworzyć odwołanie do
elementu canvas, a następnie pobrać kontekst, który będzie używany do rysowania. Gdy masz już kontekst,
możesz określić podstawowe ustawienia, takie j ak szerokość linii (za pomocą metody context . 1 i neWi dth)
czy używany kolor (za pomocą metody context . stro keSty1 e). Następnie ustawiasz odstęp między liniami
siatki na 1 00, a początkowe współrzędne na (0,0) oraz określasz liczbę linii poziomych i pionowych, które
należy narysować. Na podstawie tych informacji kreślone są następnie kolejne linie poziome, rozmieszczone
co 100 pikseli. Po ukończeniu linii poziomych od lewej do prawej kreślone są linie pionowe, każda rysowana
od góry do dołu płótna. Pamiętaj, że w przypadku kierunku y na górze znajduje się wartość O, która wzrasta
w miarę przesuwania się w dół.
Gdy linie siatki są j uż wykreślone, dodawane są punkty przecięcia i ich współrzędne. Aby nanieść punkty,
należy w każdym punkcie przecięcia narysować koło za pomocą narzędzia łuku, któremu przyjrzysz się bardziej
szczegółowo w dalszej części rozdziału. Etykiety ze współrzędnymi są wyświetlane za pomocą metody fi 1 1 Text.
Tworzen ie prostych kształtów i l i n i i 139

Domyślnie tekst będzie wyrównany do lewej i górnej krawędzi, co sprawdza się w przypadku wszystkich
punktów siatki z wyjątkiem wysuniętej najbardziej na prawo kolumny i najniższego wiersza. Gdybyś próbował
wyświetlić te etykiety, nie zmieniając wyrównania tekstu, ich zawartość nie byłaby widoczna, ponieważ byłyby
narysowane w kontekście, ale poza obszarem wyświetlania płótna. Zamiast tego skrypt określa, czy punkt
jest w ostatniej kolumnie lub w dolnym rzędzie, i odpowiednio modyfIkuje wyrównanie dla tej pozycji.

Uwaga
Pon ieważ płótno bazuje na operacjach przeprowadzanych na pikselach, w dwuwym iarowej rzeczywistości
kluczowe znaczenie ma kolejność rysowani a . W Canvas API nie istnieje pojęcie warstw, więc kolejność,
w ja kiej rozbudowujesz rysunek, wpływa na końcowy rezultat. Jeśli na przykład dodasz na płótn ie tekst
drogowskazu przed narysowan iem samego znaku posiadającego nie przezroczyste tło, Twój tekst zostanie
przykryty. Zam iast tego powin ieneś narysować tło znaku, a następnie um ieścić na n i m tekst.

Właśnie narysowałeś swój pierwszy rysunek na płótnie i j ednocześnie dowiedziałeś się, jak działa system
siatki z kontekstem płótna. Teraz spójrzmy na podstawowe kształty, które można rysować, metody oraz
niektóre efekty dostępne w ramach funkcjonalności płótna.

Narzędzia płótna
Płótno oferuje podstawowy zestaw narzędzi, za pomocą których możesz tworzyć wiele prostych i złożonych
kształtów. W kolejnym przepisie otrzymasz zwięzłe informacje o każdym z tych narzędzi i poznasz kilka
prostych sposobów ich użycia.
• Rectangle - rysuje we wskazanym miejscu prostokąt o określonej szerokości i wysokości;
• Line - tworzy linię pomiędzy punktami A i B;
• Path - tworzy ścieżkę za pomocą co najmniej jednej linii lub krzywych;
• Arc - tworzy łuk o zadanych wymiarach; jest też używany do tworzenia okręgów;
• Curve - tworzy jeden z dwóch rodzajów krzywych: Beziera lub kwadratową.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie prostych kształtów i linii
W tym przepisie narysujesz proste kształty, takie jak kwadrat i trójkąt, oraz dowiesz się, j ak rysować linie
i ścieżki.

Rysowanie i formatowanie prostokąta lu b kwad ratu


Za pomocą metody fi 1 1 Rect(poz-x, poz-y , szerokość, wysokość) możesz narysować prostokąt albo kwadrat:
canvas . fi l l Rect ( O , O, 100 , 100) ;

Spowoduje to utworzenie kwadratu o wymiarach 1 OOxl 00 pikseli i umieszczenie go w lewym górnym


rogu (0,0) płótna, tak jak pokazano na rysunku 6.2. Domyślnie kwadrat będzie czarny, co jest dość sztampowe,
więc popracujmy nad dodaniem jakiegoś koloru.
140 Rozdział 6 . Rysowanie na płótn ie

Rysunek 6 2 . Kwadrat narysowany na płótnie

Oprócz podstawowego zestawu narzędzi udostępnianych przez płótno istnieje kilka efektów, które mogą
być używane w połączeniu z narzędziami:
• Fili - określa parametry wypełnienia kształtu;
• Stroke - określa rodzaj linii używanych w kształtach;
• Gradient - pozwala na wykorzystanie liniowych albo radialnych wzorów wypełnień w kształtach;
• Transparency określa poziom przezroczystości wszystkich lub wybranych kształtów na płótnie
-

za pomocą wartości RGBA ich wypełnienia;


• Shadow - udostępnia łatwy w użyciu cień do zastosowania do indywidualnych kształtów;
• Compositing maskuje lub przycina obszary płótna i określa ogólną kolejność tworzenia
-

kształtów na płótnie.

W tym rozdziale spotkałeś się już z metodami dotyczącymi powyższych efektów. Oto niektóre z bardziej
popularnych metod dostępnych dla kształtów:
• st rokeStyl e - określa kolor lub styl dla linii wokół kształtów;
• fi 1 1 Styl e - określa kolor lub styl używany wewnątrz kształtów;
• s hadowOffsetX/shadowOffsetY - określaj ą odległość cienia względem kształtu;
• shadowBl ur - określa poziom efektu rozmycia;
• shadowCo l or - określa kolor cienia;
• createL i nearGrad i ent - tworzy gradient liniowy wewnątrz kształtu;
• createRad i a l Grad i ent - tworzy gradient radialny wewnątrz kształtu.

Jeśli więc zastosujesz kilka dostępnych efektów na kwadracie, który narysowałeś wcześniej za pomocą
fi 1 1 Rect, możesz otrzymać dość uroczy fioletowy kwadrat z fioletowym cieniem (patrz listing 6.3). W przypadku
któregokolwiek z efektów dotyczących koloru, takich jak strokeStyl e, fi 1 1 Styl e czy shadowCo l or, do określania
kolorów nie musisz używać formatu RGBA; możesz skorzystać z wartości szesnastkowych albo HSLA. Musisz
jednak pamiętać, że wszystkie style i efekty należy zastosować przed użyciem metody fi 1 1 Rect, gdyż metoda
ta nakazuje kontekstowi płótna wyświetlenie kształtu, a ponieważ kształt j est wyświetlany za pomocą
poszczególnych pikseli, nie ma sposobu, aby zmodyfikować go po narysowaniu. Aby zmodyfikować kształt,
należy go przerysować, o czym przeczytasz w dalszej części rozdziału.
Tworzen ie prostych kształtów i l i n i i 141

Listing 6 . 3 . Formatowanie kwadratu

<canvas i d= " canvas" w i dt h = " 640" hei ght = " 480 " ><jcanvas>
<scri pt>
var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
canvas . s hadowOffsetX = 1 0 ;
canvas . s hadowOffsetY = 1 0 ;
canvas . s hadowBl ur = 1 0 ;
canvas . s hadowCol or = ' rgba (200 , O , 200 , . 3 ) ' ;
canvas . fi 1 1 Styl e = ' rgba (200, O , 200, 1) ' ;
canvas . fi 1 1 Rect ( O , O , 100, 100) ;
<jscri pt>

Dostępne kody kolorów uwzględniaj ą ustawienia przezroczystości, co może być niezmiernie przydatne
podczas tworzenia obrazów na pierwszy rzut oka zawieraj ących warstwy. Na listingu 6.3 półprzezroczystej
wartości RGBA cienia użyto, aby uzyskać najbardziej pożądany efekt cienia.
Istnieją dwie inne metody związane z rysowaniem prostokątów: el earReet (poz-x , poz-y , szerokość,
wysokość) i stro keReet (poz-x, poz-y , szerokość, wysokość) . Metoda el earReet czyści piksele w danym
prostokątnym obszarze, co powoduje usunięcie w nim wszelkich zmian pikseli i przywrócenie standardowego,
przezroczystego stanu. J ak dowiesz się w dalszej części rozdziału, metoda e l ea rReet jest niezwykle pomocna
w animacji i może być wykorzystywana do wyczyszczenia całego płótna (przez użycie jego wymiarów jako
szerokości i wysokości) lub j ego fragmentu. Natomiast stro keReet w połączeniu z l ; neW; dth rysuje linię
o określonych przez Ciebie współrzędnych oraz szerokości i wysokości. Użycie kombinacji kształtów i efektów,
jak na listingu 6.4, skutkuj e jeszcze bardziej oryginalnym rysunkiem.

Listing 6.4. Więcej możliwości nadawania stylu

var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
canvas . s hadowOffsetX = 1 0 ;
canvas . s hadowOffsetY = 1 0 ;
canvas . s hadowBl ur = 1 0 ;
canvas . s hadowCol or = ' rgba (200 , O , 200 , . 3 ) ' ;
canvas . fi 1 1 Styl e = ' rgba (200, O , 200, 1) ' ;
canvas . strokeStyl e = ' #09c ' ;
canvas . l i neWidth = 5;
canvas . fi I I Rect ( O , O , 100 , 100) ;
canvas . c l earRect (25 , 2 5 , 50 , 50) ;
canvas . strokeRect(25, 25 , 50, 50) ;

Stosowanie grad ientów w kształtach


Innym efektem, który możesz zastosować do kształtów płótna, j est gradient. Gradient wypełniający płótno
może być utworzony j ako liniowy (ereateL; nearGrad ; ent) albo radialny (ereateRad ; al Grad ; ent). Aby dodać
kolory do gradientu, użyj właściwości addCo l orStop. Na listingu 6.5 tworzymy dwa prostokąty z dwoma
typami gradientów.

Listing 6 . 5 . Tworzenie gradientów

<canvas i d= " canvas" w i dt h = " 640" hei ght = " 480 " ><jcanvas>
<scri pt>
var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
var grd = canvas . createLi nearGrad i ent ( O , 200 , 200 , O) ;
142 Rozdział 6 . Rysowanie na płótn ie

grd . addCol orStop ( O , ' #000 ' ) ;


grd . addCo l orStop ( . 5 , ' #eee ' ) ;
grd . addCo l orStop ( 1 , ' #000 ' ) ;
eanvas . fi l l Styl e = grd ;
eanvas . strokeStyl e = ' #0ge ' ;
eanvas . l i neWi dth = 5 ;
eanvas . fi l l Reet ( O , O , 200, 200) ;
eanvas . e l osePath () ;

var grd = eanvas . e reateRad i al Gradi ent (300 , 2 5 0 , 2 , 200 , 200 , 250) ;
grd . addCol orStop ( O , ' #000 ' ) ;
grd . addCo l orStop ( 1 , ' #eee ' ) ;
eanvas . fi l l Styl e = grd ;
eanvas . fi l l Reet (200 , 200 , 200 , 200) ;
eanvas . e l osePath () ;
<jseri pt>

Powyższy kod tworzy w lewym górnym rogu prostokąt z gradientem liniowym, a w prawym dolnym
rogu prostokąt z gradientem radialnym. Obie metody gradientowe używają różnych parametrów do określania
zachowania gradientu:
ereateLi nearGrad i ent (startX, start Y , kon i eeX, kon i e c Y)
ereateRad i a l Grad i ent (startX, start Y , star tProm i eń , kon i eeX, kon i e c Y , kon i ecPromień)

Metoda gradientu radialnego może się wydawać nieco skomplikowana, więc warto poćwiczyć jej stosowanie;
wypróbuj różne parametry addCo l orStop, aby zobaczyć, co się stanie.

Rysowanie linii i ś cieżek


Na proces rysowania linii przy użyciu płótna składają się trzy metody:
• moveTo (x y) - przesuwa aktualną pozycję na siatce płótna do danego punktu; linia będzie
,

rysowana od tego punktu;


• l ; neTo (x ,y) - informuje płótno, jakie współrzędne będzie miał punkt końcowy linii;
• stro ke () - wywoływana, by nakazać płótnu narysowanie linii; jeśli styl kreski nie został ustawiony
za pomocą metody stro keStyl e, domyślnym kolorem linii będzie czarny.

Na listingu 6.6 kreślenie linii rozpoczynamy blisko lewego górnego rogu, w punkcie (10, 10), a za pomocą
metody l ; neTo ustawiamy koniec linii w punkcie (630, 470), w pobliżu prawego dolnego rogu płótna, j ak
pokazano na rysunku 6.3.

Listing 6 . 6 . Rysowanie linii

<eanvas i d= " eanvas" w i dt h = " 640" hei ght = " 480 " ><jeanvas>
<seri pt>
var eanvas = doeument . getEl ementByl d ( ' eanvas ' ) . getContext ( ' 2d ' ) ;
eanva s . moveTo ( l O , lO) ;
eanvas . l i neTo (630,470) ;
eanvas . stroke ( ) ;
<jseri pt>
Tworzen ie prostych kształtów i l i n i i 143

Rysunek 6.3. Linia narysowana na płótnie

Pamiętaj, że aby uwzględnić na rysunku takie własności linii jak szerokość czy kolor, należy je zdefiniować
przed wywołaniem metody stroke.
Metody l ; neTo możesz użyć wielokrotnie, aby narysować różne kształty, tworząc "ścieżkę", którą podąża
linia. Spójrzmy na proces rysowania trójkąta na listingu 6.7, na którym metodę l ; neTo wykorzystuje się trzy
razy do narysowania trzech boków trójkąta.

Listing 6 . 7 . Rysowanie trójkąta

<eanvas i d= " eanvas" w i dt h = " 640" hei ght = " 480 " ><jeanvas>
<seri pt>
var eanvas = doeument o getEl ementByl d ( ' eanvas ' ) o getContext ( ' 2d ' ) ;

eanvas o begi nPath () ;


eanva s o moveTo ( l O , lO) ;
eanvas o l i neTo (630, 470) ; IIprzeciwprostokątna
eanvas o l i neTo ( 1 0 , 470) ; II dolny bok
eanvas o l i neTo ( 1 0 , 10) ; II lewy bok
eanvas o e l osePath () ;

eanvas o strokeStyl e ' #000 ' ;


eanvas o l i neWi dth = 3 ;

eanvas o f i 1 1 Styl e = ' #eee ' ;


eanvas o fi 1 1 () ;

eanvas o stroke ( ) ;
<jseri pt>

Aby linie tworzyły jedną ścieżkę, należy użyć dwóch kolejnych metod: beg ; n Path i el osePath. beg; n Path
mówi płótnu, że kolejne linie albo krzywe należą do jednego obiektu ścieżki, który j est "zamykany" po
wywołaniu metody e l osePath. Ś cieżka złożona z linii lub krzywych może być wtedy traktowana - analogicznie
do prostokąta - jako j eden obiekt i jednolicie formatowana.
Podglądaj ąc kształt utworzony przy użyciu kodu z listingu 6.7, zauważysz, że w miej scu, gdzie koniec
linii zbiega się z j ej początkiem (lewy górny róg), linia j est nieco nierówna, gdyż oba końce nie łączą się
płynnie. Istnieje na to j ednak sposób - właściwość l ; neCap, która przyjmuj e wartości butt, round albo
square. To rozwiąże kwestię punktu końcowego, ale co z pozostałymi wierzchołkami trójkąta? Możesz użyć
do tego właściwości l ; neJ o ; n, która przyjmuje wartości beve l , m; ter albo round:
eanvas o l i neCap = ' round ' ;
eanvas o l i neJoi n = ' round ' ;
144 Rozdział 6 . Rysowanie na płótn ie

Jak się niebawem przekonasz, ścieżki mogą zawierać segmenty składające się nie tylko z linii, ale również
z krzywych.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Rysowanie wielokątów za pomocą ścieżki
w tym przepisie wykorzystasz funkcj onalność rysowania linii i ścieżek Canvas API w celu narysowania
wielokąta foremnego na podstawie liczby boków i promienia podanych przez użytkownika. Ponieważ wielokąt
jest tworzony przez zastosowanie funkcji ścieżki, możesz wypełnić kształt kolorem. Aby utworzyć wielokąt,
JavaScript wykreśli linię do każdego kolejnego wierzchołka w oparciu o określone na podstawie formuł
matematycznych współrzędne wierzchołków. Przepis może być zrealizowany za pomocą następujących
kroków i z użyciem kodu z listingu 6.8:
1. Utwórz stronę płótna z listingu 6.8 z sekcjami styl e i body.
2. Dodaj pola do wprowadzania liczby boków i promienia oraz przycisk do uruchamiania rysowania
wielokąta.
3. Dodaj funkcję ; n ; t, zmienne globalne i procedurę obsługi zdarzenia ładowania strony, aby ustawić
globalne odwołania do płótna i kontekstu.
4. Dodaj funkcję drawPo l ygon, która rysuje właściwy wielokąt foremny.

Listing 6.8. Rysowanie wielokąta za pomocą ścieżek

< ! DOCTYPE html >


<html >
<head>
<meta charset = " UT F-8" j>
< t i t l e>6 . 8 . Rysowan i e wi e l o kąta za pomocą ś c i eżek<jt i t l e>
<styl e>
#canvas {
border: lpx sol i d #03 F ;

<jstyl e>
<scri pt>
II zmienne globalne płótna i kontekstu
var canva s ;
v a r context ;

Ilfunkcja inicjująca po załadowaniu strony


funct i on i n i t ( ) {

II dołącz obsługę przycisku


var btnDrawPolygon = document . getEl ementByld ( ' drawPol ygon ' ) ;
btnDrawPol ygo n . addEvent L i s t ener ( ' c l i c k ' , drawPol ygon , fal se) ;

II dołącz odwołania do płótna i kontekstu


canvas = document . getEl ementByld ( ' canvas ' ) ;
context = canvas . getContext ( ' 2d ' ) ;

IIfunkcja rysująca w ielokąt na płótnie


funct i on drawPo l ygon ( ) {
Rysowanie wielokątów za pomocą ścieżki 145

IIpobierz parametry wielokąta od użytkownika


var numS i des = document . getEl ementByl d ( ' numS i des ' ) . val ue ;
var radi us = document . getEl ementByl d ( ' ra d i us ' ) . va l u e ;

Ilwyznacz środek płótna. by umieścić wielokąt w centrum


var xCenter parselnt (canvas . wi dthj2 ) ;
var yCenter = parselnt (canvas . he i ghtj2 ) ;

II wyczyść płótno
context . cl earRec t ( O , O , canvas . wi dt h , canvas . he i g ht ) ;

II rozpocznij ścieżkę
context . begi nPath () ;

II mapuj wierzchołek. od którego zaczynamy


var xPos = xCenter + rad i us * Mat h . cos (2 * Mat h . PI * O j numS i des ) ;
var yPos = yCenter + rad i us * Math . s i n (2 * Mat h . PI * O j numS i des ) ;
context . moveTo (xPos ,yPos ) ;

II iteruj przez wierzchołki i mapuj linie


for ( i = 1 ; i <= numS i de s ; i ++)

II określ współrzędne następnego wierzchołka


xPos xCenter + radi us * Mat h . cos (2 * Math . PI * j numS i des ) ;
yPos = yCenter + radi us * Mat h . s i n (2 * Math . PI * j numS i des ) ;

IIpoprowadź linię do następnego wierzchołka


context . l i neTo(xPos ,yPos ) ;

II zamknij ścieżkę linii


context . cl osePath () ;

II określ właściwości linii i narysuj linie


contex t . l i neW i dt h = 3 0 ;
context . l i neJo i n = ' round ' ;
contex t . stroke () ;

Ilwypełnij nowo utworzony wielokąt


context . fi l l Styl e = ' #OO F ' ;
contex t . fi 1 1 ( ) ;

Ilwywołajfunkcję init po załadowaniu strony


wi ndow . addEventLi stener ( ' l oad ' , i n i t , fal se) ;

<jscri pt>
<jhead>
<body>
<h 1>Użyc i e śc i eżek pfótna : <jh1>
<canvas i d = " canvas " wi dth= " 400" he i ght="400 " >
El ement canvas n i e j est obsfugi wany w Twojej przegl ądarc e .
<jcanvas>
<br>
Li czba boków: <i nput t ype= II number" i d = " numS i des" m i n = " 3 " step= " l " val ue= " 7 " /><br>
Prom i e ń : < i nput type= II number" i d = " rad i us " m i n= " IO " step= " l " val ue= " 150" /><br>
<button i d= " drawPo l ygon">Narysuj wi e l o kąt<jbutton>
<jbody>
<jhtml >
146 Rozdział 6 . Rysowanie na płótn ie

Po kliknięciu przycisku Narysuj wielokqt zostaj e wywołana funkcja d rawPo l ygon. Funkcja ta pobiera
najpierw od użytkownika dane dotyczące liczby boków i promienia wielokąta. Następnie znajduje na płótnie
punkt środkowy, aby umieścić wielokąt w centrum płótna. Wreszcie płótno zostaje przy użyciu funkcji
c l ea rRect wyczyszczone, tak aby za każdym razem, gdy użytkownik tworzy nowy wielokąt, był on rysowany
na pustym płótnie.
Teraz, gdy płótno j est przygotowane, rozpoczynamy ścieżkę za pomocą metody beg; nPath. Następnie
przy użyciu pewnych wymyślnych algorytmów wyznaczamy początkowe współrzędne (x, y) i przechodzimy
w pętli przez każdy wierzchołek, używając do tworzenia odcinków metody l ; neTo. Po wykreśleniu wszystkich
odcinków ścieżka jest zamykana za pomocą metody cl osePath. Metoda ta łączy wszystkie odcinki w jedną
ścieżkę, której następnie używamy do narysowania linii i wypełnienia kształtu, w wyniku czego na płótnie
rysowany jest wielokąt. Rysunek 6.4 prezentuje przykład.

Liczba boków: 5

�rnrim � l�
� -------"�

I Narysuj wielokąt I
Rysunek 6.4. Wielokąt o pięciu bokach i promieniu 150
utworzony z wykorzystaniem funkcjonalności ścieżki

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Rysowanie łuków i okręgów
Nie j esteś ograniczony wyłącznie do rysowania na płótnie linii prostych; możesz też dodawać krzywe.
Zacznijmy od metody arc, która przyjmuje następujące parametry:
canvas . arc (x , y, prom i eń , kqtPoczqtek, kqtKon i e c , przec i wn i eDoRuchuWskazówek (Bool ean) ) ;

Spójrzmy na przykład z listingu 6.9, który daje efekt pokazany na rysunku 6.5.

Listing 6 . 9 . Rysowanie łuku

<canvas i d= " canvas" w i dt h = " 640" hei ght = " 480 " ><jcanvas>
<scri pt>
var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
Dodawanie tekstu 147

canvas . arc ( lO O , 100 , 40, 5 , 1 , true) ;


canvas . strokeStyl e ' #000 ' ;
=

canvas . l i neWi dth 5; =

canvas . stroke ( ) ;
<jscri pt>

Rysunek 6 . 5 . Łuk narysowany na płótnie

Na listingu 6.9 wartość prze c i wn i eDoRuchuWskazówek została ustawiona na true. Spróbuj ustawić ją na
fal se i zobacz, co jest rysowane.
Aby narysować koło, użyj metody arc ( ) , wpisując jako kąt początkowy o, a jako końcowy 2*Math . P I :
canvas . arc ( lO O , 1 5 0 , 60, O , 2 * Mat h . P I , fal se) ;
canvas . fi l l Styl e = ' #000 ' ;
canvas . fi l l () ;
canvas . strokeStyl e =' #000 ' ;
canvas . l i neWi dth =2;

Rysowanie krzywych
Na płótnie wykorzystywane są dwa rodzaj e krzywych: quadrat i cCurveTo i bez i erCu rveTo . Różnią się one
tym, że quadrat i cCurveTo posiada jeden punkt kontrolny, natomiast bez i erCurveTo dwa. Punkty kontrolne
-

umożliwiają dodawanie krzywych do linii, dzięki czemu można tworzyć bardziej złożone kształty:
canvas . quadrat i cCurveTo (pX, p Y , kon i eeX, kon i e c Y) ;
canvas . bezi erCurveT o (plX, p l Y , p2X, p2Y, kon i eeX, kon i ec Y) ;

PRZEPIS DLA POCZĄTKUJĄCYCH


Dodawanie tekstu
Canvas API oferuje dwie metody dodawania do płótna tekstu: fi 1 1 Text i strokeText. Metoda fi 1 1 Text
pobiera ciąg znaków do wyświetlenia razem ze współrzędnymi (x, y) określającymi miejsce, w którym tekst
ma zostać wyświetlony, i tworzy napis z wypełnionymi literami, natomiast metoda st rokeText przyjmuje
te same parametry, ale dodaje obrys znaków:
fi l l Text ( teks t , x, y) ;

W obu przypadkach rodzaj czcionki, jej grubość oraz rozmiar można zmieniać za pośrednictwem
właściwości font, a styl wypełnienia i obramowania może być kontrolowany poprzez właściwości fi 1 1 Styl e
i strokeStyl e, co pokazano na listingu 6 . 1 0 .
148 Rozdział 6 . Rysowanie na płótn ie

Listing 6 . 10 . Dodawanie tekstu do płótna

<scri pt>
var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
canvas . font = ' bol d 80px Tahoma ' ;
canvas . fi l l Styl e = ' #000 ' ;
canvas . fi l l Text ( ' HTML5 Canvas ' , 1 0 , 100) ;

canvas . strokeStyl e = ' #000 ' ;


canvas . l i neWi dth = 3 ;
canvas . f i 1 1 Styl e = ' #ccc ' ;
canvas . textA l i gn = ' center ' ;
canvas . fi l l Text ( ' HTML5 Canvas ' , 3 2 0 , 200) ;
canvas . strokeText ( ' HTML5 Canvas ' , 3 2 0 , 200) ;
<jscri pt>

Listing 6 . 1 0 daje efekt pokazany na rysunku 6.6. Zauważ, że w wywołaniach metod fi 1 l Text i stro keText
po ciągu znaków podane są współrzędne x i y miejsca, gdzie tekst zostanie narysowany. W drugim przykładzie
tekst jest dodatkowo wyrównany do środka i wykorzystane są zarówno fi 1 1 Text, jak i strokeText, więc jeśli
chciałbyś otrzymać wyłącznie obrys tekstu, usuń wywołanie metody fi 1 1 Text.

HTM LS Ca nva s
D=Olr[fq] [b� �@) ITUW@)@
Rysunek 6 . 6 . Tekst narysowany n a płótnie

PRZEPIS DLA POCZĄTKUJĄCYCH


Wstawianie obrazka
Jednym z bardziej interesuj ących aspektów Canvas API j est wstawianie obrazków. Za pomocą metody
drawlmage możesz wstawić wybrany przez siebie obrazek i na wiele sposobów nim manipulować. W prostym
przykładzie z listingu 6 . 1 1 wczytywany jest plik PNG efekt jest widoczny na rysunku 6.7.
-

Listing 6.11. Umieszczanie obrazka na płótnie

<canvas i d= " canvas" wi dt h = " 34 2 " hei ght= " 25 6 " ><jcanvas>
<scri pt>
var canvas = document . getEl ementByl d ( ' canvas ' ) . getContext ( ' 2d ' ) ;
var canvas lmage = new Image () ;

funct i on drawCanvaslmage () {
canva s . drawlmage (canvas lmage, 43 , O) ;
};
Przycinanie obrazka 149

canvas lmage . addEventLi stener ( ' l oad ' , drawCanvaslmage , fal se) ;

canvas lmag e . src = ' imagesjhtm1 5 - 1 ogo. png ' ;


<jscri pt>

Rysunek 6 . 7 . Obrazek umieszczony na płótnie


(logo HTML5 jest dziełem organizacji W3C, www.w3. org)

Funkcja drawlmage posiada trzy różne postacie:


• drawlmage (obrazek , x , y) - wyświetla obrazek podany w adresie URL obrazka na pozycji x orazy;
• drawlmage (obrazek , x , y , s , w) - skaluje obraz w oparciu o szerokość (s) i wysokość (w);
• drawlmage (obrazek, xl , y1 , 51 , w1 , x2 , y2 , 52 , w2) - umieszcza fragment obrazka
źródłowego określony parametrami xl , y1 , 51 , w1 w punkcie określonym parametrami x2, y2,
nadając mu wymiary określone parametrami 52 i w2.

W wersji podstawowej metoda draw lmage pobiera adres URL obrazu, a następnie współrzędne x i y
punktu, w którym obrazek ma być wyświetlony. Obraz nie jest w żaden sposób przeskalowany ani przycięty.
Zdarzenie l oad z listingu 6 . 1 1 służy do sprawdzenia, czy przed próbą wyświetlenia obrazka na płótnie został
on załadowany. Jeśli przekażesz adres URL obrazka bezpośrednio do zmiennych, nic się nie wyświetli,
ponieważ obraz nie będzie jeszcze załadowany na stronie. Aby zatem upewnić się, że obraz został załadowany,
przed próbą jego wyświetlenia nasłuchuj e się zdarzenia załadowania obrazka.
Jak widać, obraz na rysunku 6.7 z trudnością mieści się we wnętrzu ramki płótna. Rozmiar obrazu można
zmienić za pomocą metody drawlmage z uwzględnieniem skalowania szerokości i wysokości:
drawlmage (obrazek , x, y, szerokość, wysokość) ;

W ten sposób skrypt uwzględnia dodatkowo wartości szerokości i wysokości - przykładowo wywołanie
canva s . drawlmage(canvaslmage , 50 , 50 , 150 , 2 1 1 ) ; zmieni rozmiar obrazu na 150 pikseli szerokości i 2 1 1
pikseli wysokości oraz umieści go w punkcie oddalonym o 5 0 pikseli o d góry i o d lewej strony.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Przycinanie obrazka
Obraz można przyciąć przy użyciu trzeciej wersji metody, obejmującej parametry przycinania. Parametrami
przycinania są początkowe położenie x i y na obrazku oraz szerokość i wysokość ramki kadrowania. N a
pierwszy rzut oka jest to nieco skomplikowane, więc listing 6 . 1 2 zawiera komentarze do każdej wartości.
150 Rozdział 6 . Rysowanie na płótn ie

Listing 6.12. Przycinanie obrazka

funct i on croplmage ( ) {
canvas . drawlmage (canvaslmage,
O, IIpozycja x ramki kadrowania n a źródłowym obrazku
O, IIpozycja y ramki kadrowania n a źródłowym obrazku
168 , II szerokość ramki kadrowania na źródłowym obrazku
2 3 6 , Ilwysokość ramki kadrowania na źródłowym obrazku
1 1 O , IIpozycja x ramki na płótnie
1 1 O , IIpozycja y ramki na płótnie
2 5 0 , II szerokość ramki na płótnie
250 Ilwysokość ramki na płótnie
);
};

canvas lmage . addEventLi stener ( ' l oad ' , c roplmage , fal se) ;

Poza typowym wstawianiem obrazka na płótnie API umożliwia również wykorzystanie obrazka do
wypełnienia danego kształtu. Można to zrealizować dzięki metodzie createPattern. Najpierw przekazywany
jest obrazek, który chcemy wyświetlić, a następnie tworzona jest zmienna wzorca z obrazkiem. Na podstawie
zadanych współrzędnych i rozmiaru rysowany j est kształt. Styl wypełnienia kształtu zostaj e nadany na
podstawie utworzonego wcześniej wzoru, jak pokazano poniżej :
var canvas lmage = new Image () ;
funct i on createlmagePattern () {
var pattern = canvas . createPattern (canvas lrnage , ' repeat ' ) ;
canvas . rect ( O , O , 640, 480) ;
canvas . fi l l Styl e =pattern ;

canvas . fi l l ( ) ;
};
canvas lmage . addEvent Li stener ( ' l oad ' , c reatelmagePattern , fal se) ;

canvas lmage . s rc = ' imagesjhtm1 5 - 1 ogo. png ' ;

Parametr repeat może przyjąć następujące wartości:


• repeat - powtarza wzór poziomo i pionowo,
• repeat-x - powtarza wzór poziomo,
• repeat-y - powtarza wzór pionowo,
• no-repeat - nie powtarza wzoru.

PRZEPIS DLA Ś REDN I O ZAAWANSOWANYCH


Animacja mapy sprite'ów
Skoro metoda drawlmage może być używana do przycinania obrazka źródłowego i wstawiania wynikowego
fragmentu obrazka na płótnie, możesz j ej również użyć do animowania map sprite'ów przez cykliczne
przesuwanie się przez kadry obrazka sprite'a. Metoda drawlmage jest wywoływana w ustalonych odstępach
czasu i każdorazowo wyświetla nowy kadr obrazka źródłowego, tworząc tym samym animację. W tym
przepisie wykorzystasz prostą mapę trzech kadrów sprite' ów do animacji ikony recyklingu. Za każdym
Animacja mapy sprite’ów 151

razem, kiedy wywoływana jest metoda drawImage, ramka jest przesuwana do następnej klatki. Po osiągnięciu
ostatniej klatki ramka jest cofana do pierwszej klatki źródłowego obrazka. Aby zrealizować ten przykład,
wykorzystaj następujące kroki oraz listing 6.13:
1. Utwórz stronę z listingu 6.13 ze znacznikami style i body, płótnem oraz przyciskami
do włączania i wyłączania animacji.
2. Dodaj definicje zmiennych globalnych, funkcję init oraz procedurę obsługi zdarzeń
window.addEventHandler.
3. Dodaj funkcję animateSprite, która rysuje przycięte zdjęcie.
4. Dodaj funkcje startAnimation i stopAnimation, powiązane z przyciskami do włączania
i wyłączania animacji.

Listing 6.13. Przycinanie obrazka z mapami sprite’ów

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>6.13. Animacja sprite’a</title>
<style>
#canvas {
/* umieść obramowanie wokół płótna */
border:1px solid #03F;
}
</style>
<script>
// zmienne canvas i context służące do rysowania
var canvas;
var context;

// obiekt przechowujący obrazek animacji


var spriteRecycle = new Image();

// zmienne ramki obrazka sprite’a


var sliceX = 0;
var sliceY = 0;
var sliceWidth = 100;
var sliceHeight = 100;

// zmienna animacji
var intervalRef;

// inicjalizacja płótna
function init() {
// ustaw procedury obsługi przycisków
var btnStart = document.getElementById('start');
var btnStop = document.getElementById('stop');
btnStart.addEventListener('click',startAnimation,false);
btnStop.addEventListener('click',stopAnimation,false);

// uzyskaj referencje do płótna i kontekstu


canvas = document.getElementById('canvas');
context = canvas.getContext('2d');

// przypisz źródło obrazka


spriteRecycle.src = 'recycle_sprite.png';
152 Rozdział 6. Rysowanie na płótnie

// animuj obrazek w oparciu o kadr


function animateSprite() {
// narysuj obrazek w oparciu o bieżący kadr sprite’a
context.drawImage(spriteRecycle, sliceX, sliceY, sliceWidth, sliceHeight, 0, 0, 100, 100);

// przesuń kadr sprite’a


sliceX+=100;
// ustaw pierwszy kadr, jeśli to konieczne
if (sliceX>=spriteRecycle.width) {
sliceX = 0;
}
}

// rozpocznij animację, ustawiając pauzę


function startAnimation() {
if (!intervalRef) {
intervalRef = setInterval('animateSprite()',100);
}
}

// zakończ animację, usuwając ustawioną pauzę


function stopAnimation() {
clearInterval(intervalRef);
// wyczyść płótno za pomocą clearRect
context.clearRect(0,0,100,100);
intervalRef = null;
}

// wywołaj funkcję init po załadowaniu strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Animacja mapy sprite’ów na płótnie</h1>
<canvas id="canvas" width="100" height="100">
Element canvas nie jest obsługiwany w tej przeglądarce.
</canvas>
<br>
<button id="start">Włącz</button>
<button id="stop">Wyłącz</button>
</body>
</html>

Jeżeli po wywołaniu funkcji startAnimation użytkownik kliknie przycisk Włącz, funkcja ustawia interwał
pomiędzy uruchomieniami funkcji animateSprite na wartość 100 milisekund. Oto istota taktowania animacji:
dopóki nie zostanie wywołana funkcja stopAnimation, która wyczyści wartość interwału, co 100 milisekund
będzie wywoływana funkcja animateSprite. Za każdym razem, kiedy zostanie ona wywołana, zostanie
wyświetlony odpowiedni kadr obrazka recyklingu. Obrazek recyklingu ma 300 pikseli szerokości i zawiera
trzy kadry o wymiarach 100×100 pikseli. Po każdym kolejnym kadrowaniu ramka jest przesuwana o 100
pikseli w prawo, jak pokazano na rysunku 6.8. Jeśli kadrowanie wypada na ostatnich 100 pikselach, ramka
jest przesuwana z powrotem na 0.
Przekształcenia płótna 153

Rysunek 6.8. Kadry obrazka sprite’a wyświetlane w każdym cyklu

Zauważ, że czyszczenie płótna przed każdym wywołaniem drawImage nie jest konieczne, gdyż obrazek
sprite’a ma nieprzezroczyste tło, a za każdym razem rysujesz na całym płótnie o wymiarach 100×100 pikseli.

Uwaga
Mimo że element canvas nie obsługuje warstw na samym płótnie, istnieje sposób na odzwierciedlenie
funkcjonalności warstw. Jako że płótno jest elementem, który może być formatowany i ma domyślnie
przezroczyste tło, nic nie powstrzymuje programistów przed definiowaniem wielu elementów canvas
spozycjonowanych jeden na drugim. Aby kontrolować położenie warstw na stronie, należy wykorzystać
na elemencie atrybut stylu z-index. Nietrudno wyobrazić sobie grę, która ma płótno tła, podczas gdy
sprite postaci jest ładowany na mniejszym płótnie umieszczonym na innych płótnach. Jeśli implementujesz
stronę z wieloma płótnami, zwróć uwagę na kwestię wydajności, ponieważ do używania poszczególnych
elementów canvas konieczne jest utrzymywanie odwołań do nich.

Przekształcenia płótna
Oprócz narzędzi i efektów na płótnie dostępny jest zestaw narzędzi transformacyjnych. Oto trzy narzędzia
przekształcania:
„ rotate(kąt) — obraca leżącą u jego podstaw siatkę kontekstu płótna tak, że wszystko, co jest
dodawane do obróconego kontekstu, użytkownik widzi jako obrócone; kąt jest podawany
w radianach i jest wyznaczany zgodnie z kierunkiem ruchu wskazówek zegara.
„ scale(x, y) — umożliwia programiście sterowanie skalą jednostek x i y; metoda ta przyjmuje
jako parametry jednostkę skali x i jednostkę skali y.
„ transform — pozwala programiście na dostęp bezpośrednio do macierzy transformacji.

W dokonywaniu przekształceń pomocne są trzy związane z płótnem metody. Metoda translate(x, y)


pozwala przenieść punkt początkowy płótna do nowego miejsca. Jeśli zatem wywołałbyś metodę
translate(100,100), punkt początkowy zostałby przesunięty o 100 pikseli w dół i 100 pikseli w prawo,
stając się nowym punktem początkowym. To niezwykle istotne przy przekształceniach takich jak obrót,
ponieważ punkt początkowy jest punktem, wokół którego transformacja (obrót) się odbywa. Istnieją dwa
sposoby przywrócenia punktu początkowego do pierwotnej pozycji. Pierwszym jest po prostu ponowne
154 Rozdział 6. Rysowanie na płótnie

użycie metody translate w celu przesunięcia pozycji w przeciwnym kierunku. Drugą i preferowaną metodą
jest użycie metod save i restore.
Metody save i restore pozwalają na zapisanie bieżącego kontekstu przed dokonaniem przekształcenia
oraz późniejsze przywrócenie zapisanej wersji kontekstu. Ten sposób wykorzystamy w różnych przepisach
wraz z metodą rotate, żeby zademonstrować transformacje na płótnie. Metoda rotate przyjmuje liczbę
radianów, o jaką ma obrócić kierunek płótna. Aby przekształcić kąt w stopniach na radiany, należy użyć
następującego wzoru:

kąt w radianach = kąt w stopniach * π / 180

Dzięki wykorzystaniu takich przekształceń jak metody rotate i scale możesz użyć płótna do utworzenia
ciekawych animacji, jak pokazano w kolejnym przepisie.

Uwaga
Jak w przypadku każdego podstawowego zestawu wywołań API, które nadają się do wykonywania
złożonych i powtarzalnych funkcji, takich jak rysowanie kwadratów czy okręgów, w oparciu o podstawowe
składniki, zostały opracowane rozmaite rozszerzone biblioteki wywołań. Część z nich funkcjonuje
jako samodzielne produkty i może stanowić podstawę do opakowywania funkcji płótna w prostsze
wywołania. Biblioteki te mają zazwyczaj rozbudowaną funkcjonalność, co ułatwia realizację bardziej
złożonych zadań. Niektóre z nich warto przetestować: netron, canvas toolkit, EaselJS, jCanvaScript
czy gury. Jesteśmy zresztą pewni, że wraz ze zwiększaniem się popularności płótna HTML5 wzrastać
będzie także liczba i funkcjonalność bibliotek.

PRZEPIS DLA ZAAWANSOWANYCH


Animowanie obrazka
W tym przepisie wykorzystamy dwa kroki do „przetoczenia” koła zębatego po płótnie od lewej do prawej.
Pierwszym krokiem będzie przesunięcie obrazka przez przerysowywanie go co jakiś czas coraz bardziej po
prawej stronie. Drugim krokiem będzie obrócenie obrazka o pewną liczbę stopni za każdym razem, gdy jest
on przesuwany. Jak pokazano wcześniej, efekt obracania służy do obracania kontekstu rysunku. W tym
przykładzie zapiszemy domyślny kontekst płótna, następnie obrócimy go, narysujemy obrazek i wreszcie
przywrócimy wcześniejszy kontekst. Dzięki połączeniu tych metod kod z listingu 6.14 pozwoli uzyskać
złudzenie, że koło zębate toczy się po ekranie.
1. Utwórz stronę z listingu 6.14 zawierającą płótno oraz znaczniki style i body.
2. Dodaj definicje zmiennych globalnych, funkcję init oraz procedurę obsługi zdarzeń
window.addEventHandler.
3. Dodaj funkcję moveGear, która obraca, przesuwa i rysuje obrazek koła zębatego.

Listing 6.14. Przesuwanie i obracanie obrazka

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>6.14. Poruszające się koło zębate</title>
<style>
Animowanie obrazka 155

#canvas {
/* zastosuj proste obramowanie do płótna */
border:1px solid #03F;
}
</style>
<script>

// zmienne referencyjne canvas i context służące do rysowania


var canvas;
var context;

// odwołanie do obrazka koła zębatego


var gear = new Image();

// bieżąca pozycja x obrazka


var xpos;

// zmienne animacji
var stepCounter; // licznik bieżącego kroku
var stepDegrees; // o ile obrócić w następnym kroku
var stepDistance; // jak daleko przesunąć obrazek w każdym kroku
var stepSpeed; // jak szybko obracać i przesuwać obrazek
var stepsFullRevolution; // ile kroków jest w pełnym obrocie

// inicjalizuj szerokość i wysokość planszy


function init() {
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');

// inicjalizuj licznik kroków obrotów


stepCounter = 0;
stepDegrees = 2;
stepDistance = 2;
stepSpeed = 5;
stepsFullRevolution = parseInt(360 / stepDegrees);

// dodaj procedurę nasłuchu zdarzenia załadowania obrazka


gear.addEventListener('load',initGear,false);

// ustaw źródłowy obrazek koła zębatego


gear.src = 'gear.png';
}

// po załadowaniu obrazka rozpocznij odtwarzanie animacji


function initGear(){
// ustaw początkową pozycję x tuż poza lewą stroną płótna
xpos = -(gear.width/2);
// wywołaj funkcję animacji
moveGear();
};

// funkcja usuwająca poprzedni obraz koła zębatego i rysująca nowy


function moveGear() {

// usuń stare koło zębate z płótna


context.clearRect(0, 0, canvas.width, canvas.height);

// zapamiętaj obecny kontekst płótna, aby móc do niego powrócić


context.save();
156 Rozdział 6. Rysowanie na płótnie

// zmień położenie
xpos += stepDistance;

// przesuń punkt 0,0 do nowego położenia koła zębatego


context.translate(xpos,canvas.height-(gear.width/2));

// obróć kontekst, a tym samym koło zębate


context.rotate(Math.PI * stepDegrees * stepCounter / 180);

// narysuj nowo obrócony obrazek


context.drawImage(gear, -(gear.width/2), -(gear.height/2), gear.width, gear.height);

// przywróć kontekst do pierwotnego położenia


context.restore();

// sprawdź, czy koło zębate opuściło płótno po prawej stronie


if ((xpos-(gear.width/2)) < canvas.width) {

// powiększ licznik kroków i sprawdź, czy został osiągnięty pełen obrót


stepCounter++;
if (stepCounter>=(stepsFullRevolution-1)) {
stepCounter=0;
}

// nadal widać koło zębate — nie zatrzymuj się


setTimeout('moveGear()',stepSpeed);
}
}

// wywołaj funkcję init po załadowaniu strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Toczące się koło zębate</h1>
<canvas id="canvas" width="600" height="100">
Element canvas nie jest obsługiwany w tej przeglądarce.
</canvas>
</body>
</html>

Kiedy po załadowaniu obrazu wywoływana jest funkcja moveGear, pierwszym wykonywanym przez kod
działaniem jest wyczyszczenie płótna przez wywołanie metody clearRect. Należy to zrobić, żeby „resztki”
poprzednio narysowanego obrazka nie pozostały na płótnie. Następnie skrypt zapisuje bieżący kontekst,
zwiększa wartość zmiennej położenia w poziomie i za pomocą funkcji translate umieszcza punkt początkowy
w nowym miejscu. W kolejnym kroku w oparciu o nowy punkt początkowy skrypt za pomocą metody rotate
obraca obraz o ustawioną wcześniej wartość stepDegrees. Wreszcie obrazek jest rysowany, a ukierunkowanie
kontekstu przywracane na potrzeby następnej funkcji rysującej.
Po narysowaniu obrazka koła zębatego w nowym miejscu i pod nowym kątem, porównując położenie
w poziomie i szerokość płótna, sprawdzamy, czy obraz został przeniesiony poza prawą krawędź płótna.
Jeżeli tak się stało, animacja zostaje zatrzymana. Jeśli jednak obraz jest nadal widoczny na płótnie, skrypt
zwiększa licznik kroków i określa kolejny czas przerwania, aby ponownie uruchomić funkcję moveGear.
Wszystkie ustawienia animacji można regulować poprzez zmianę zmiennych animacji na początku
skryptu. Rysunek 6.9 przedstawia różne etapy animacji obrazu na płótnie.
Animacja pionowego wykresu słupkowego 157

Rysunek 6.9. Kadry animacji obrazka sprite’a w miarę wyświetlania w każdym cyklu

Uwaga
W interakcje z płótnem — czy to grając w gry, czy przesuwając kształty — możesz wejść poprzez
przechwytywanie zdarzeń myszy, takich jak mouseover i click. Współrzędne podane w zdarzeniu
mogą być odwzorowywane przez przesunięcie współrzędnych x i y w stosunku do pozycji płótna na
stronie. Jeśli na przykład (x, y) ze zdarzenia kliknięcia myszy to (150, 200), a przesunięcie płótna
to 100 dla x i 100 dla y, wtedy współrzędne kliknięcia myszką na płótnie to (50, 100). Znając te
współrzędne na siatce płótna, możesz ustalić, czy kliknięcie miało miejsce na konkretnym kształcie
narysowanym na płótnie.

PRZEPIS DLA ZAAWANSOWANYCH


Animacja pionowego wykresu słupkowego
W ostatnim przepisie tego rozdziału użyjemy narzędzi płótna oraz wybranych efektów i przekształceń
w celu utworzenia na podstawie zbioru przykładowych danych JSON pionowego wykresu słupkowego.
Po załadowaniu wykresu pionowe słupki będą rosły do zadanych wartości na podstawie zdefiniowanych
ustawień animacji, takich jak prędkość wzrostu i rozmiar każdej kolumny. Rysunek 6.10 przedstawia rezultat
z przykładowymi danymi po tym, jak animacja słupków została zakończona. Aby zrealizować ten przepis,
prześledź następujące kroki i kod z listingu 6.15:
1. Utwórz stronę z listingu 6.15 z płótnem o identyfikatorze graph i znacznikami style oraz body.
2. Dodaj definicje zmiennych globalnych włącznie z obiektem JSON chartData, funkcję initGraph
oraz procedurę obsługi zdarzeń window.addEventHandler.
3. Dodaj funkcję initSettings, która określa wszystkie właściwości wykresu, w tym liczbę i rozmiar
słupków.
4. Dodaj funkcję drawAxis, która doda osie współrzędnych x i y, etykiety danych na osiach oraz
nazwy osi.
5. Dodaj funkcję growBars, która animuje wzrost pionowych słupków do ich właściwej wysokości.
6. Dodaj pomocniczą funkcję drawBar, która odpowiada za rzeczywiste rysowanie słupka na płótnie.

Listing 6.15. Tworzenie animowanego pionowego wykresu słupkowego

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>6.15. Rosnący wykres słupkowy</title>
158 Rozdział 6. Rysowanie na płótnie

<style>
#graph {
/* obramuj płótno */
border:1px solid #03F;
}
</style>
<script>

// zmienne płótna i kontekstu rysowania


var canvas;
var context;

// ustawienia wykresu
var chartMargin;
var chartAxisSpace;
var chartWidth;
var chartHeight;

// zmienne dotyczące słupków


var numBars = 0; // całkowita liczba słupków
var barMargin = 20; // margines pomiędzy słupkami
var barWidth = 0; // szerokość słupka
var maxValue = 0; // maksymalna wartość danych słupków

// liczba etykiet osi y


var numYLabels;

// zmienne dotyczące animacji słupków


var idxStep;
var numSteps;
var growSpeed;

// przykładowe dane wykresu w postaci JSON


var chartData = {'bars':[
{'title':'1. rok','value':'7'},
{'title':'2. rok','value':'12'},
{'title':'3. rok','value':'20'},
{'title':'4. rok','value':'33'},
{'title':'5. rok','value':'55'},
{'title':'6. rok','value':'93'},
{'title':'7. rok','value':'156'}
]}

// inicjalizuj szerokość i wysokość planszy


function initGraph() {

// pozyskaj odwołanie do płótna i kontekstu rysowania


canvas = document.getElementById('graph');
context = canvas.getContext('2d');

initSettings(); // inicjalizuj ustawienia wykresu


drawAxis(); // narysuj osie wykresu i ich etykiety
growBars(); // animuj słupki wykresu
}

function initSettings() {

// ustawienia wykresu
chartMargin = 20; // margines wokół całego płótna
chartAxisSpace = 50; // obszar na osie x i y
Animacja pionowego wykresu słupkowego 159

// ustawienia obszaru rysowania wykresu


chartHeight = canvas.height-chartAxisSpace-2*chartMargin;
chartWidth = canvas.width-chartAxisSpace-2*chartMargin;

// ustaw liczbę etykiet używaną wobec osi y


numYLabels = 8;

// ustaw liczbę słupków na podstawie danych chartData


numBars = chartData.bars.length;

// znajdź maksymalną wartość w danych i przeskaluj wykres


for (var i=0; i < numBars; i++) {
if (chartData.bars[i].value > maxValue) {
maxValue = parseInt(chartData.bars[i].value);
}
}

// określ szerokość każdego słupka


barWidth = (chartWidth / numBars)-barMargin;

// inicjalizuj zmienne dotyczące animacji


idxStep = 0;
numSteps = 100;
growSpeed = 6;
}

function drawAxis() {
// ustaw grubość linii odpowiadającą szerokości osi
context.lineWidth = 2;

// narysuj oś y — od lewej dolnej strony do lewej górnej


context.moveTo(chartMargin+chartAxisSpace,chartHeight+chartMargin);
context.lineTo(chartMargin+chartAxisSpace, chartMargin);
context.stroke();

// narysuj oś x — od lewej dolnej strony do prawej dolnej


context.moveTo(chartMargin+chartAxisSpace, chartMargin+chartHeight);
context.lineTo(chartMargin+chartAxisSpace+chartWidth, chartMargin+chartHeight);
context.stroke();

// ustaw grubość linii z powrotem na 1 piksel


context.lineWidth = 1;

// dodaj etykiety danych na osi y


var markerAmount = parseInt(maxValue / numYLabels);
context.textAlign = 'right';
context.fillStyle = '#000';
// dodaj w pętli etykiety na osi y
for (var i=0; i <= numYLabels; i++) {
// ustal etykietę i punkty x i y
markerLabel = i*markerAmount;
markerXPos = chartMargin + chartAxisSpace - 5;
markerYPos = chartMargin + (chartHeight - ((i*markerAmount*chartHeight)/maxValue));

// dodaj etykiety tekstowe na ustalonych pozycjach


context.fillText(markerLabel, markerXPos, markerYPos, chartAxisSpace);
}

// dodaj etykiety każdego słupka w oparciu o dane wykresu


context.textAlign = 'center';
160 Rozdział 6. Rysowanie na płótnie

// dodaj w pętli tytuł każdego słupka


for (var i=0; i<numBars; i++) {
// określ pozycje x i y etykiet
markerXPos = chartMargin+chartAxisSpace + barMargin + (i * (barWidth+barMargin)) + (.5*barWidth);
markerYPos = chartMargin+chartHeight + 10;
// dodaj tekst pod słupkiem
context.fillText(chartData.bars[i].title, markerXPos, markerYPos, barWidth);
}

// dodaj tytuł osi y


// zapamiętaj obecny kontekst
context.save();
// przesuń punkt (0,0) do punktu tytułu osi y
context.translate(chartMargin+10,chartHeight/2);
// obróć bieżący kontekst rysowania w kierunku przeciwnym do ruchu wskazówek zegara o 90 stopni
context.rotate(Math.PI*-90 / 180);
// dodaj tekst tytułu
context.fillText('Sprzedaż (w tys.)',0,0);
// przywróć orientację kontekstu rysowania
context.restore();

// dodaj tytuł osi x


context.fillText('Rok sprzedaży',chartMargin+chartAxisSpace+(chartWidth/2),chartMargin+chartHeight+40);
}

// funkcja animująca wzrost słupków w kierunku pionowym


// wywoływana co jakiś czas w ustalonej liczbie kroków
function growBars() {

// deklaruj x, y i h słupka
// szerokość słupka została określona wcześniej
var barStartX = 0;
var barStartY = 0;
var barHeight = 0;

// wartość słupka ze zbioru danych


var barValue = 0;

// narysuj w pętli każdy słupek w zależności od kroku


for (var i=0; i < numBars; i++) {

// uzyskaj wartość słupka


barValue = parseInt(chartData.bars[i].value);

// oblicz wysokość słupka i punkty początkowe x i y


barHeight = (barValue * chartHeight / maxValue) / numSteps * idxStep;
barStartX = chartMargin + chartAxisSpace + (i * (barWidth + barMargin)) + barMargin;
barStartY = chartMargin + (chartHeight-barHeight);

// wywołaj funkcję pomocniczą rysującą słupek


drawBar(barStartX, barStartY, barWidth, barHeight);
}

// powiększ słupki, jeśli jeszcze nie przestały rosnąć


if (idxStep<numSteps) {
idxStep++;
setTimeout('growBars()',growSpeed);
}
}
Animacja pionowego wykresu słupkowego 161

// funkcja pomocnicza rysująca słupek w oparciu o przekazane jej wymiary


// można przekazać jej kontekst i inne parametry w celu dostosowania
function drawBar(barX, barY, barW, barH) {
// utwórz prostokąt z wypełnieniem
context.fillStyle = '#00c';
context.fillRect(barX, barY, barW, barH);

// dodaj cień do słupka


context.shadowOffsetX = 3;
context.shadowOffsetY = -3;
context.shadowBlur = 3;
context.shadowColor = 'rgba(200, 200, 200, .3)';

// dodaj obramowanie do słupka


context.strokeStyle = '#000';
context.lineWidth = 1;
context.strokeRect(barX, barY, barW, barH);
}

// po załadowaniu strony inicjalizuj wykres słupkowy


window.addEventListener('load',initGraph,false);

</script>
</head>
<body>
<h1>Rosnący wykres słupkowy</h1>
<canvas id="graph" width="600" height="400">
Ta przeglądarka nie obsługuje elementu canvas.
</canvas>
</body>
</html>

Rysunek 6.10. Ukończony pionowy wykres słupkowy z przykładowymi danymi

Po załadowaniu strony zostanie wywołana funkcja initGraph, która z kolei wywoła funkcję initSettings,
a ta zainicjalizuje wszystkie ustawienia wykresu. Funkcja initGraph wywoła następnie funkcję drawAxis.
Funkcja drawAxis narysuje dwie linie, będące osiami x i y, a następnie w oparciu o liczbę oznaczeń określoną
162 Rozdział 6. Rysowanie na płótnie

podczas inicjalizacji doda oznaczenia danych do osi y. W przypadku osi x dla każdego słupka zostanie
wykorzystana właściwość title danych JSON wykresu — zauważ, że etykieta każdego słupka jest wyrównywana
względem jego środka. Wreszcie funkcja doda tytuł osi x oraz tytuł osi y, który jest obrócony o 90 stopni
w kierunku przeciwnym do kierunku ruchu wskazówek zegara.
Funkcja initGraph wywoła następnie funkcję growBars. Funkcja growBars jest sercem tej prostej animacji
i zwiększa krok animacji w celu określenia na podstawie wartości końcowej słupka z chartData, o ile procent
powiększyć każdy słupek. Innymi słowy, każdy słupek jest proporcjonalnie zwiększany za każdym razem,
kiedy wykonywana jest funkcja growBars. Po obliczeniu nowych wymiarów każdego słupka funkcja growBars
wywoła pomocniczą funkcję drawBar z zadanymi wymiarami słupka, aby faktycznie narysować słupek na
płótnie. Kiedy już funkcja growBars przejdzie przez wszystkie słupki wykresu, sprawdzi, czy krok ten był
ostatnim krokiem w animacji. Jeśli kroków jest więcej, funkcja ustawia czas ponownego wywołania funkcji
growBars po inkrementacji indeksu kroku. Rysunek 6.11 pokazuje, w jaki sposób wykres słupkowy rośnie
w kolejnych kadrach animacji.

Rysunek 6.11. Wzrost kolumn wykresu słupkowego

Aby zmienić prędkość albo tempo wzrostu słupków, wystarczy zmodyfikować zmienne numSteps
i growSpeed w funkcji initSettings.

Wskazówka
Jak widzisz, tworzenie animowanych, a nawet interaktywnych wykresów za pomocą płótna HTML5
jest całkiem proste. Jeśli chcesz poznać nieograniczone możliwości wykorzystania płótna HTML5 do
tworzenia wykresów — od podstawowych wykresów liniowych po skomplikowane wykresy w kształcie
róży, możesz się zapoznać z biblioteką wykresów RGraph (http://www.rgraph.net/). Twórcy biblioteki
wykonali ciężką pracę, a biblioteka wykorzystuje potencjał płótna w powiązaniu z JavaScriptem. Dopóki
postępujesz zgodnie z umową licencyjną, jeśli tylko chcesz, możesz ją włączać do własnych projektów.

Podsumowanie
W tym rozdziale poznałeś Canvas API. API to ma w kwestii rysowania do zaoferowania o wiele więcej, niż
zostało opisane. Jednak znając podstawy, dysponujesz narzędziami do zgłębiania bardziej zaawansowanych
metod i zagadnień Canvas API. Zachęcamy Cię do wykorzystywania jego pełnych możliwości oraz wykazania
się własną artystyczną kreatywnością.
7
Osadzanie wideo w HTML5

J esteśmy pewni, że jeśli ostatnich kilku lat nie spędziłeś w pustelni, korzystałeś z witryn takich jak YouTube
i Vimeo, innymi słowy, z witryn z osadzoną na stronie zawartością wideo. Chociaż wyżej wymienione
serwisy są najpopularniejszymi witrynami do udostępniania wideo, umieszczaliśmy je online w ten czy inny
sposób od jakichś dziesięciu lat. Problemem było to, że wobec braku standardu prezentacji wideo musieliśmy
używać wtyczek, takich jak Flash, lub używać elementu object do odtwarzania plików QuickTime.
Możesz uważać, że problem nie istnieje, ponieważ powyższe rozwiązania na ogół dobrze się sprawdzają.
Problemem jest jednak to, że przy udostępnianiu filmów nie należy polegać na zewnętrznych wtyczkach.
W HTML5 znajduje się nowy, ustandaryzowany sposób natywnego renderowania wideo na stronie
internetowej — możesz użyć elementu video.
W tym rozdziale omówimy podstawowe zagadnienia związane z elementem video, a następnie zajmiemy
się niektórymi kodekami oraz, tradycyjnie, pewnymi problemami związanymi z ich odmiennym działaniem
w różnych przeglądarkach. Dowiesz się także, jak poprawić przystępność wideo za pomocą napisów i podpisów,
a na końcu użyjesz nowego API mediów do utworzenia własnego odtwarzacza wideo i kontrolek.

PRZEPIS DLA POCZĄTKUJĄCYCH


Dołączanie filmów za pomocą elementu video
Aby poznać bardzo prosty sposób na umieszczenie na stronie filmu przy użyciu nowego elementu video,
przyjrzyj się kodowi z listingu 7.1.

Listing 7.1. Prosty sposób użycia elementu video

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>7.1. Prosty sposób użycia elementu video</title>
</head>
<body>
<video src="mojfilm.mp4"></video>
<!--
video to samozamykający się element, więc może on być również użyty w sposób następujący
164 Rozdział 7. Osadzanie wideo w HTML5

<video src="mojfilm.mp4" />


-->
</body>
</html>

Proste, prawda? Na podstawowym poziomie to wszystko, czego potrzebujesz, aby użyć elementu video
w HTML5. Niestety, w rzeczywistości nie jest to takie proste. Teraz, kiedy zaostrzyliśmy Twój apetyt,
omówimy kilka kwestii, o których powinieneś wiedzieć, zanim zaczniesz korzystać z video.

Obsługa w przeglądarkach i urządzeniach


Tabela 7.1 stanowi zestawienie wersji poszczególnych przeglądarek kompatybilnych z elementem video.
Zakładamy, że kompatybilność dotyczy niżej opisanych oraz wszystkich nowszych wersji przeglądarek.

Tabela 7.1. Obsługa elementu video

Android 2.1+
Chrome 10.0+
Firefox 3.6+
Internet Explorer 9.0+
iOS Safari 3.2+
Opera 10.6+
Safari 4.0+

Jak widać, z jednej strony element video jest powszechnie obsługiwany w aktualnych wersjach przeglądarek,
ale z drugiej strony nie jest natywnie obsługiwany przez Internet Explorer w wersjach 6, 7 i 8, a te przeglądarki
mają ogromny udział w rynku. W dalszej części rozdziału pokażemy, jak opracować zastępczą treść dla
przeglądarek i urządzeń, które jeszcze nie obsługują elementu video. Zanim jednak do tego dojdziemy,
powinieneś się zapoznać z różnymi rodzajami kodeków wideo.

HTML5 i kodeki wideo


Obsługa wideo w HTML5 okazała się wielkim sukcesem, widocznym szczególnie w dużych witrynach
z filmami, takich jak YouTube i Vimeo. Jednak przed HTML5 stoi duże wyzwanie — kwestia kodeków.
Jeśli kiedykolwiek przesyłałeś filmy na YouTube, mogłeś nie zwrócić uwagi na to, w jakim formacie są one
zapisane, ponieważ YouTube akceptuje i konwertuje praktycznie każdy format pliku wideo. W przypadku
HTML5 należy jednak wziąć pod uwagę trzy główne kodeki i formaty wideo (patrz tabela 7.2).

Tabela 7.2. Kodeki i ich obsługa w przeglądarkach

Internet
Kodek Android Chrome Firefox iOS Safari Opera Safari
Explorer
H.264 - 13+ - 9+ 4+ - 5+
Ogg Theora - 13+ 5+ - - 11+ -
WebM 2.3 13+ 5+ * - 11+ -
* poprzez wtyczkę: http://www.webmproject.org/ie/
Dołączanie filmów za pomocą elementu video 165

Początkowo w specyfikacji HTML5 dążono do tego, aby wszystkie przeglądarki obsługiwały kodeki Ogg
Theora (Ogg Theora jest kodekiem wideo, Ogg Vorbis — audio). Jednak wobec protestów firm Apple i Nokia
kodeki te zostały usunięte ze specyfikacji, by nigdy do niej nie powrócić. Nie umieszczono w niej również
żadnych innych sugestii odnośnie do kodeków.
Obecnie kodek Ogg Theora obsługują natywnie przeglądarki Chrome i Firefox. Safari posiada natomiast
natywne wsparcie jedynie dla kodeka H.264, który jest również obsługiwany przez Chrome oraz urządzenia
iPhone i iPad.
W maju 2010 roku firma Google poinformowała o pracach nad innym formatem multimedialnym,
projektem WebM (znanym również jako VP8). Jest to kodek typu open source, a jego obsługa została
szybko zaimplementowana w przeglądarkach Chrome, Android, Firefox i Opera.
Z kolei Internet Explorer 9 wspiera natywnie kodek H.264, a WebM będzie obsługiwał tylko za
pośrednictwem dodatkowo instalowanego kodeka, co nie jest idealnym rozwiązaniem.

Dlaczego kodeki powinny Cię interesować?


Kwestie związane z kodekami będą jeszcze przez jakiś czas stanowiły źródło problemów. Największy dotyczy
kodeka H.264. Kodek ten, znany również jako MPEG-4, wymaga od dostawców i użytkowników produktu,
czyli także od Ciebie, o ile Twoja witryna go używa, uiszczania opłat licencyjnych. W sierpniu 2010 roku
ogłoszono, że użytkownicy nie będą obciążani opłatami za używanie filmów wideo H.264, ale spory prawne
nadal trwają i trzeba otwarcie powiedzieć, że sprawa licencjonowania tego kodeka wciąż nie jest wyjaśniona.
Natomiast zarówno Ogg, jak i WebM są bezpłatnymi kodekami typu open source, które są uważane za
formaty „sieci otwartej”. Chrome obsługiwał wcześniej H.264, ale w styczniu 2011 roku firma Google podbiła
stawkę w bitwie o wideo i ogłosiła, że obsługa kodeka H.264 zostanie zarzucona, a zamiast tego będzie
implementowany kodek wideo WebM. Istnieją różne teorie związane z przyczynami, dla których Google
podjęło tę decyzję, ale w tej książce nie będziemy analizować szczegółów. Warto wspomnieć, że YouTube
(własność Google) będzie nadal konwertować wideo na format H.264, ale może też przekonwertować
wszystkie wideo na WebM. Na tym etapie nie wiadomo natomiast, jak z formatem H.264 będą współpracowały
urządzenia z systemem Android (również należącym do Google). Wideo na urządzeniach przenośnych
to zresztą potężny segment rynku, który będzie stale rósł, więc warto śledzić to zagadnienie.
Jak widać, w związku z tak wielką liczbą przeglądarek i urządzeń oraz związanymi z tym strategiami
działania firm kodeki są dużym wyzwaniem. Skutki decyzji firmy Google mogą być daleko idące, ale tak
naprawdę jeszcze nie wiemy jak bardzo. Nie wiemy również, jak ostatecznie rozwinie się Internet Explorer 9,
pozostaje zatem sporo niewiadomych.
Niech jednak nie zrażą Cię ewentualne problemy z kodekami — jest jeszcze sporo rzeczy, które możesz
zrobić z zawartością wideo w HTML5.

Wskazówka
Świetnie, dowiedziałeś się o różnych formatach, ale jak możesz je utworzyć? Cóż, popularne pakiety
oprogramowania wideo nie eksportują obecnie ani do Ogg, ani do WebM, ale wyeksportują plik H.264.
iPhone eksportuje plik .mov, a większość kamer cyfrowych wyeksportuje filmy do plików .mov albo .avi.
Dostępne jest oprogramowanie do konwersji plików wideo, w tym Firefogg — rozszerzenie do Firefoksa,
które koduje wideo Ogg, albo Handbrake do kodowania wideo H.264. My polecamy MiroVideoConverter
(http://www.mirovideoconverter.com/) — bezpłatny, łatwy w użyciu program, który konwertuje filmy
na kilka różnych typów wideo.
166 Rozdział 7. Osadzanie wideo w HTML5

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Włączanie wideo we wszystkich przeglądarkach
Jak wspomnieliśmy w pierwszym przepisie niniejszego rozdziału, najprostszym sposobem użycia elementu
video jest zastosowanie następujących znaczników:
<video src="mojfilm.mp4"></video>

Ze względu na problemy z przeglądarkami i kodekami w jednym elemencie video należy zadeklarować


różne formaty wideo, czego można dokonać za pomocą wielu elementów source. Aby upewnić się, że Twoje
wideo będzie działać w najnowszych wersjach najważniejszych przeglądarek, powinieneś zadeklarować plik
w formacie .mp4 oraz w formacie .webm albo .ogv, jak na listingu 7.2.

Listing 7.2. Użycie elementu source do wyświetlenia różnych formatów wideo

<video width="640" height="480" controls>


<source src="video.mp4" type="video/mp4" />
<source src="video.webm" type="video/webm" />
<source src="video.ogv" type="video/ogg" />
</video>

Uwaga
W idealnym świecie przeglądarki i urządzenia wybierałyby po prostu pierwszy plik, który mogłyby
odtworzyć. Tak zwykle dzieje się w najpopularniejszych przeglądarkach w komputerach stacjonarnych
i na laptopach, ale błąd przeglądarki iPada uniemożliwia jej załadowanie czegokolwiek oprócz pierwszego
źródła wideo. Dlatego właśnie plik .mp4 należy zadeklarować jako pierwszy, jak na listingu 7.2.

Element video posiada atrybuty height i width, choć w celu ustawienia jego wysokości i szerokości
możesz także użyć CSS-a. Posiada również atrybut controls, który wyświetla domyślne kontrolki wideo.
Na listingu 7.2 w obrębie elementu video znajdują się trzy elementy source. Każdy z nich wskazuje na
pojedyncze wideo i zawiera atrybut type, który mówi przeglądarce, jakiego typu plik jest dostarczany.
Jeśli przeglądarka nie rozpoznaje typu, nie będzie pobierać pliku, dzięki czemu nie będzie niepotrzebnie
wykorzystywać zasobów sieci, a strona będzie się szybciej ładować. Przeglądarka wybierze pierwszy plik,
który będzie w stanie odtworzyć.
Kod z listingu 7.2 zapewnia wyświetlenie wideo w przeglądarkach Chrome, Firefox, Internet Explorer 9
i Safari oraz na iPadzie, iPhonie i urządzeniach z systemem Android, a wszystko to bez potrzeby instalowania
zewnętrznych komponentów, co jest świetną wiadomością. Ale co można zaoferować użytkownikom
korzystającym z przeglądarek Internet Explorer 6, 7 i 8, które nie obsługują w tej chwili znacznika video?

Dodawanie zastępczej treści dla starszych przeglądarek


Po elementach source można umieścić treść, która ostrzega użytkowników, gdy ich przeglądarka nie potrafi
obsłużyć elementu video. Taka treść jest umieszczana w obrębie elementu video, a w kwestii samego
komunikatu masz do wyboru kilka możliwości. Mógłbyś na przykład zamieścić prosty komunikat, taki jak
pokazany poniżej:
<p>Przepraszamy, Twoja przeglądarka jest bardzo stara. Prosimy, abyś ją uaktualnił.</p>
Włączanie wideo we wszystkich przeglądarkach 167

Możesz też być bardziej pomocny i zaoferować bezpośrednie pobieranie filmu wideo:
<a href="video.mp4">Pobierz nasz film w formacie MP4</a>
<a href="video.webm">Pobierz nasz film w formacie WebM</a>
<a href="video.ogv">Pobierz nasz film w formacie Ogg</a>

Swoją drogą, udostępnienie możliwości bezpośredniego pobierania jest dobrym pomysłem


i prawdopodobnie powinieneś je zaoferować wszystkim użytkownikom, niezależnie od ich urządzeń,
więc umieścimy ten kod w naszym ostatecznym rozwiązaniu.
Treść zastępcza jest przeznaczona głównie dla użytkowników, którzy surfują, używając starych wersji
Internet Explorera. Ponieważ Flash ma ogromny udział w rynku i jest zainstalowany na dużej części
komputerów, możesz względnie bezpiecznie oferować treść Flash jako treść zastępczą. Masz do wyboru
dwie podstawowe możliwości: możesz wysłać wideo do YouTube albo użyć własnego programu Flash Player.
Korzystanie z YouTube znacznie upraszcza cały proces oraz rozwiązuje problemy hostingowe. Ponadto
YouTube dostarcza Ci kod HTML potrzebny do osadzenia filmu na stronie, jak pokazano na listingu 7.3.

Listing 7.3. Kod do osadzenia wideo z YouTube

<object width="480" height="385">


<param name="movie" value="http://www.youtube.com/v/VIDEO_ID"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/VIDEO_ID" type="application/x-shockwave-flash"
´allowscriptaccess="always" allowfullscreen="true" width="480" height="385">
</embed>
</object>

Złożenie tego wszystkiego razem daje Ci kod z listingu 7.4, który udostępnia wideo we wszystkich
najpopularniejszych aktualnie przeglądarkach — czy to za pośrednictwem natywnego elementu video,
czy też awaryjnie za pomocą treści Flash.

Listing 7.4. Wideo z treścią zastępczą działające w różnych przeglądarkach

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>7.4. Wideo z treścią zastępczą działające w różnych przeglądarkach</title>
</head>
<body>
<video width="640" height="480" controls>

<!-- wideo dla Safari i IE9; ze względu na iPada MP4 musi być pierwszym formatem -->
<source src="cablecar.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
<!-- wideo dla Chrome, Firefoksa i Opery -->
<source src="cablecar.webm" type="video/webm" />
<source src="cablecar.ogv" type='video/ogg; codecs="theora, vorbis"' />

<!-- zastępcza treść dla starszych przeglądarek o mniejszych możliwościach -->


<object width="480" height="385">
<param name="movie" value="http://www.youtube.com/v/ZR-H-FQDenw"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
168 Rozdział 7. Osadzanie wideo w HTML5

<embed src="http://www.youtube.com/v/ZR-H-FQDenw" type="application/x-shockwave-flash"


´allowscriptaccess="always" allowfullscreen="true" width="480" height="385">
</embed>
</object>
</video>
</body>
</html>

Nie musisz wybierać serwisu YouTube jako witryny przechowującej wideo; plik możesz przechowywać
samodzielnie, a do jego odtwarzania używać własnego odtwarzacza Flash. Mógłbyś zaprojektować
odtwarzacz samodzielnie albo użyć jednego z wielu dostępnych w internecie, takich jak popularny, łatwy
do skonfigurowania JW Player, jak pokazano na listingu 7.5.

Listing 7.5. Zastępcze wideo niezwiązane z witryną YouTube

<video width="640" height="480" controls>


<!-- wideo dla Safari i IE9; ze względu na iPada MP4 musi być pierwszym formatem -->
<source src="video.mp4" type="video/mp4" />
<!-- wideo dla Chrome, Firefoksa i Opery -->
<source src="video.webm" type="video/webm" />
<source src="video.ogv" type="video/ogg" />

<!-- zastępcza treść dla starszych przeglądarek o mniejszych możliwościach, wykorzystująca JW Playera -->
<script type='text/javascript' src='swfobject.js'></script>
<div id='mediaplayer'></div>
<script type="text/javascript">
var so = new SWFObject('player.swf','playerID','480','270','9');
so.addParam('allowfullscreen','true');
so.addParam('allowscriptaccess','always');
so.addVariable('file', 'video.mp4');
so.write('mediaplayer');
</script>
</video>

Wskazówka
Jednym z problemów, które mogą się pojawić w trakcie tworzenia treści wideo w HTML5, jest kwestia
typów MIME. Przy opracowywaniu przykładów do tej książki natknęliśmy się na ten problem po
załadowaniu plików .ogv i .webm na serwer WWW, kiedy próbowaliśmy odtworzyć je w Firefoksie.
Filmy odtwarzały się poprawnie na naszym lokalnym komputerze, a także w Operze i Chrome po
umieszczeniu ich na serwerze WWW (Apache), natomiast nie działały w Firefoksie. Wynika to z faktu,
że Firefox musi wiedzieć, iż serwer obsługuje określony typ MIME. Nie wszystkie serwery obsługują
nowe formaty wideo, a żeby to robiły, musisz dokonać odpowiednich zmian konfiguracyjnych, takich jak
dodanie następujących wierszy w pliku .htaccess albo httpd.conf, w zależności od platformy serwera:
AddType video/mp4 .mp4
AddType video/ogg .ogv
AddType video/webm .webm

Mamy już teraz kompletny kod umożliwiający obsługę wideo we wszystkich najpopularniejszych
przeglądarkach i urządzeniach. Dostępnych jest jednak kilka dodatkowych nowych atrybutów.
Włączanie wideo we wszystkich przeglądarkach 169

Nowe atrybuty wideo


Dla elementu video dostępnych jest kilka nowych atrybutów. W poprzednich przykładach używałeś atrybutów
width i height, które nie są wymagane, ale zalecamy ich stosowanie, gdyż skraca to czas ładowania strony.
Określenie tych wartości nie spowoduje rozciągnięcia ani zniekształcenia Twojego wideo, ponieważ element
video umieszcza film w środku okienka, więc jeżeli zadeklarujesz wymiary 200×1000, przeglądarka zmniejszy
odpowiednio szerokość wideo, ale zachowa jego proporcje.

Atrybut src
Listing 7.1 z początku tego rozdziału pokazuje bardzo prosty przykład elementu video zawierającego atrybut
src. Użycie src oznacza jednak, że jesteś ograniczony do jednego pliku wideo, więc wobec problemów
z kodekami i przeglądarkami atrybut ten jest obecnie w zasadzie bezużyteczny. Na potrzeby odtwarzania
filmów w różnych przeglądarkach, zamiast korzystać z atrybutu src, rozważ umieszczenie w obrębie video
elementów source, tak jak na listingu 7.4.

Atrybut poster
Atrybut poster jest używany do wyświetlenia w miejscu wideo pojedynczego obrazka, w czasie gdy jest ono
pobierane albo po prostu podczas oczekiwania na jego rozpoczęcie. Obrazek poster ma pozwolić widzowi
zorientować się, co to za film wideo albo o czym on jest. Obrazek może być plikiem .gif, .jpg albo .png (choć
przezroczysty plik PNG nie przesłoni wideo).
Jeśli atrybut poster nie jest określony, przeglądarka wyświetla po prostu pierwszą klatkę filmu, co może
w zupełności odpowiadać Twoim potrzebom. Jeśli jednak tak nie jest, ustaw obrazek poster w następujący
sposób:
<video width="640" height="480" poster="poster.gif">

Rysunek 7.1 pokazuje, jak to wygląda podczas ładowania strony.

Rysunek 7.1. Obrazek poster wyświetlony przez Google Chrome


170 Rozdział 7. Osadzanie wideo w HTML5

Warto śledzić rozwój sytuacji w kwestii konieczności umieszczenia dla obrazka poster tekstu
alternatywnego alt. Nic nie jest jeszcze przesądzone, ale istnieją pewne zastrzeżenia dotyczące obsługi,
więc atrybut poster może w przyszłości ulec zmianie.

Atrybut preload
Jeśli na stronie umieściłeś względnie duży plik wideo lub też jesteś pewien, że użytkownik będzie chciał
obejrzeć osadzony przez Ciebie na stronie filmik (tak jak w przypadku strony YouTube), możesz nakazać
przeglądarce rozpoczęcie pobierania pliku wideo natychmiast po wczytaniu strony. Atrybut preload może
przyjąć trzy możliwe wartości:
preload="auto"

Wartość auto (możesz też po prostu umieścić samo preload) informuje przeglądarkę, aby pobrała film
po załadowaniu strony.
preload="none"

Wartość preload="none" mówi przeglądarce, aby nie pobierała filmu. Dopiero kiedy użytkownik uruchomi
wideo, przeglądarka zacznie je pobierać.
preload="metadata"

Atrybut metadata pobiera informacje (metadane) o wideo, w tym jego czas trwania, pierwszy kadr, wymiary
i listę odtwarzania. Przeglądarka nie powinna pobrać samego wideo, dopóki użytkownik go nie uruchomi.

Atrybut audio (obecnie nieobsługiwany)


Chociaż przeglądarki nie obsługują aktualnie atrybutu audio, w teorii istnieje on po to, żebyś mógł sterować
domyślną głośnością wideo. Obecnie ma tylko jedną określoną wartość — muted. Gdy zostanie zastosowany,
wygląda następująco:
<video height="300" width="300" audio="muted">

</video>

W zamyśle po użyciu tego atrybutu film zostałby domyślnie wyciszony, a użytkownik musiałby zwiększyć
poziom głośności. Być może w przyszłości na potrzeby regulacji domyślnej głośności dźwięku będą dostępne
inne wartości, takie jak audio="2" albo audio="low". W jednym z kolejnych przepisów dowiesz się, jak można
zmienić głośność za pomocą JavaScriptu.

Atrybut loop
Jeśli element video posiada atrybut loop, po zakończeniu odtwarzania rozpocznie się ono od początku.
Atrybut ten jest atrybutem logicznym — albo jest włączony, albo wyłączony.

Atrybut autoplay
Atrybut logiczny autoplay wymusza na przeglądarce rozpoczęcie pobierania i odtwarzania wideo po
wczytaniu strony. W ten sposób działają na przykład strony YouTube i wiele odtwarzanych automatycznie
reklam wideo. Istnieją pewne obawy związane z dostępnością i łatwością wykorzystania odtwarzanych
Włączanie wideo we wszystkich przeglądarkach 171

automatycznie filmów wideo, nie wspominając już o tym, jak filmy te mogą być irytujące, ale atrybut
autoplay znajduje się w specyfikacji ze względu na to, że pozwala w ustandaryzowany sposób wymusić
automatyczne odtwarzanie wideo, gdybyś naprawdę tego potrzebował. Nie musisz się uciekać do różnych
javascriptowych sztuczek, aby osiągnąć ten efekt. Zauważ, że dostępne są też różne rozszerzenia przeglądarek
wyłączające autoplay.

Uwaga
W systemie iOS atrybuty autoplay i preload zostały celowo wyłączone ze względu na potencjalne
koszty i problemy związane z prędkością wczytywania u użytkownika. Żadne dane nie są pobierane,
dopóki użytkownik nie zdecyduje się ręcznie uruchomić filmu. Istnieje kilka technik (a raczej sztuczek)
pozwalających na obejście tej blokady, ponieważ jednak są to techniki nieoficjalne, nie powinny być
wykorzystywane i nie zostały omówione w tej książce.

Atrybut controls
Atrybut controls dodaje do wideo domyślne, właściwe dla danej przeglądarki kontrolki zawierające przycisk
odtwarzania/pauzy, pasek przewijania, informację o czasie trwania i czasie od początku filmu oraz kontrolki
sterowania głośnością.
Jest to atrybut logiczny, więc albo jest dołączony, albo nie. Oto przykład:
<video height="300" width="300" controls>

</video>

Jeśli element video nie zawiera atrybutu controls, zostanie wyświetlony pierwszy kadr filmu lub obrazek
poster. Użytkownik będzie musiał kliknąć film prawym przyciskiem myszy i skorzystać z listy opcji. Lepiej
jest zatem umieścić kontrolki za pomocą atrybutu controls. Jeżeli chcesz, możesz wykorzystać swoje własne
kontrolki — o tym podyskutujemy w dalszej części tego rozdziału. Rysunek 7.2 przedstawia domyślne kontrolki
w głównych przeglądarkach.

Rysunek 7 2. Domyślne kontrolki w Chrome (na górze), Firefoksie,


Internet Explorerze 9, Operze i Safari

Uwaga
Choć to dziwne, jeśli w Firefoksie nie masz włączonej obsługi JavaScriptu, kontrolki nie są wyświetlane.
Inne przeglądarki wyświetlają kontrolki poprawnie bez pomocy JavaScriptu.
172 Rozdział 7. Osadzanie wideo w HTML5

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Tworzenie wideo z napisami
W celu zwiększenia przystępności wideo możesz na przykład dołączyć napisy, podpisy i opisy, aby pomóc
osobom niesłyszącym lub niedosłyszącym.
Dostępny jest element track, w ramach którego możesz użyć różnych typów plików. Oto on w swojej
podstawowej postaci:
<track src="subtitles.vtt" kind="subtitles" srclang="pl" label="polski">

Atrybut kind może przybierać różne wartości:


„ subtitles — zapis albo tłumaczenie dialogu;
„ captions — podobnie do subtitles, ale zawiera również efekty dźwiękowe i inne
informacje audio;
„ descriptions — w zamyśle osobny plik audio, który opisuje wideo;
„ chapters — w zamyśle ma pomóc użytkownikowi poruszać się po wideo;
„ metadata — informacje i treści dotyczące wideo, które w zamyśle nie mają być wyświetlane
w przeglądarce.

Element track jest nowym dodatkiem do specyfikacji HTML5. Standardem dla napisów i podpisów był
pierwotnie WebSRT, ale jego rolę ma przejąć Web Video Text Track (WebVTT). Ponieważ technologia ta
jest nowa i wciąż wymaga ogromu pracy, nie pojawiły się jeszcze jej działające przykłady ani implementacje
w przeglądarkach. Możesz jednak wyświetlać napisy za pomocą JavaScriptu oraz pliku WebVTT (.vtt).
Listing 7.6 przedstawia prosty przykład z hipotetycznymi danymi z pliku WebVTT (w oparciu o obecną
specyfikację). Możesz utworzyć i edytować pliki .vtt, używając prostego edytora tekstu, takiego jak Notatnik.
Plik .vtt zawiera identyfikator, czas, w jakim ma być wyświetlana treść, a następnie samą treść, która powinna
zostać wyświetlona.

Listing 7.6. Przykładowa zawartość pliku .vtt

0
00:00:0,000 --> 00:00:2,000
To jest pierwszy fragment napisów

1
00:00:3,000 --> 00:00:5,000
To jest drugi fragment

2
00:00:7,000 --> 00:00:15,000
Jak mogłeś zgadnąć, to jest trzeci fragment napisów

Ponieważ żadna przeglądarka nie obsługuje jeszcze natywnie elementu track, do wykonania tego zadania
potrzebujesz javascriptowej wtyczki. Na listingu 7.7 zastosowano dostępną na stronie https://github.com/
icelab/jquery-videosub wtyczkę jQuery o nazwie VideoSub, która jest oparta o oryginalny skrypt MooTools
Thomasa Sturma (http://www.storiesinflight.com/js_videosub/). Wtyczka opakowuje video w element div,
Tworzenie wideo z napisami 173

a następnie dodaje kolejny div, zawierający napisy. W tym przykładzie domyślnie ukryjesz napisy i dodasz
łącze pozwalające je wyświetlić. Rysunek 7.3 przedstawia przykład działania napisów. Kompletny kod znajduje
się na listingu 7.7.

Rysunek 7.3. Napisy wyświetlane na filmie wideo

Listing 7.7. Napisy wideo

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>7.7. Napisy wideo</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script src="jquery.videosub.js"></script>
<script>
$(function(){
$('video').videoSub({
containerClass : 'videosub-container',
barClass : 'videosub-bar',
useBarDefaultStyle : false
});

$('.videosub-container').append('<a title="pokaż/ukryj napisy wideo" id="subtitlestoggle" href="#">Pokaż


´napisy</a>');

$('.videosub-bar').hide();

$('#subtitlestoggle').click(function() {
$(this).text($(this).text() == 'Pokaż napisy' ? 'Ukryj napisy' : 'Pokaż napisy');
$('.videosub-bar').toggle();
});
});
</script>
<style>
body {
font-family: arial, Arial, Helvetica, sans-serif;
}
.videosub-container {
width: 640px;
}
174 Rozdział 7. Osadzanie wideo w HTML5

.videosub-bar {
background: black;
bottom: 40px;
color: yellow;
font-size: 1.3em;
font-weight: bold;
padding: 10px 20px;
position: absolute;
text-align: center;
width: 560px;
}
a#subtitlestoggle {
background: black;
color: yellow;
display: block;
font-weight: bold;
padding: 10px;
position: absolute;
right: 0;
text-decoration: none;
top: 0;
}
a#subtitlestoggle:hover {
text-decoration: underline;
}
</style>

</head>
<body>
<video width="640" height="480" controls>
<!-- wideo dla Safari i IE9; ze względu na iPada MP4 musi być pierwszym formatem -->
<source src="cablecar.mp4" type="video/mp4" />

<!-- wideo dla Chrome, Firefoksa i Opery -->


<source src="cablecar.webm" type="video/webm" />
<source src="cablecar.ogv" type="video/ogg" />

<track src="subtitles.vtt" kind="subtitles" srclang="pl" label="polski">

<!-- rozwiązanie awaryjne dla kiepskich przeglądarek (YouTube itd.) -->


</video>

<p>Pobierz wideo: <a href="cablecar.mp4">plik MP4, 3 MB</a>; <a href="cablecar.webm">plik webM,


´3 MB</a>; <a href="cablecar.ogv">plik Ogg, 3 MB</a></p>
<p>Pobierz napisy: <a href="subtitles.vtt">plik VTT, 1 kB</a>; <a href="subtitles.txt">plik
´tekstowy, 1 kB</a></p>

</body>
</html>

Inne możliwości formatowania napisów


Listingi 7.6 i 7.7 przedstawiały prosty przykład użycia WebVTT, wtyczki jQuery i podstawowego CSS-a
do wyświetlania i formatowania napisów. Istnieją alternatywy, które kiedyś będą obsługiwane przez
przeglądarki. Przy użyciu wewnętrznych znaczników b, i oraz u możesz dodać do napisów pogrubienie,
kursywę albo podkreślenie:
API mediów 175

1
00:00:0,000 --> 00:00:2,000
<b>To jest</b> <i>pierwszy fragment</i> <u>napisów</u>

Możesz również zastosować klasę CSS, gdybyś chciał dołączyć inne formatowanie — na przykład użyć
niestandardowej czcionki. Aby to zrobić, należy dodać wewnętrzny znacznik c z jakąś klasą CSS:
1
00:00:0,000 --> 00:00:2,000
<c.myclassname>To jest</c> pierwszy fragment napisów

Dostępnych jest więcej możliwości, takich jak położenie czy wielkość tekstu. Aby znaleźć więcej informacji
na temat tego rozwijającego się standardu, sprawdź witrynę http://www.delphiki.com/webvtt/.

API mediów
HTML5 udostępnia rozwinięte i ekscytujące API dla multimediów. Korzystając z tego API i JavaScriptu,
możesz wpływać na działanie wideo na stronach internetowych. Poniżej znajduje się lista dostępnych
zdarzeń API:
abort loadstart
canplay pause
canplaythrough play
canshowcurrentframe playing
dataunavailable progress
durationchange ratechange
emptied seeked
empty seeking
ended suspend
error timeupdate
loadeddata volumechange
loadedmetadata waiting

Dostępnych jest również wiele właściwości mediów:


audioTracks muted
autoplay networkState
buffered paused
controls preload
controller played
currentSrc playbackRate
currentTime readyState
defaultMuted seekable
defaultPlaybackRate seeking
duration startOffsetTime
ended src
error textTracks
initialTime videoTracks
loop volume

Nie wszystkie z powyższych zdarzeń i właściwości są już dostępne, ale większości, w tym tych istotnych,
które pozwalają tworzyć własne kontrolki odtwarzacza wideo, można już używać. Nie będziemy tu wyjaśniać
wszystkich możliwości API — ograniczymy się tylko do potrzebnych do utworzenia własnego odtwarzacza;
176 Rozdział 7. Osadzanie wideo w HTML5

jeśli jednak jesteś zainteresowany zdobyciem większej wiedzy na temat tego API, polecamy materiały na stronie
http://www.w3.org/2010/05/video/mediaevents.html oraz bardzo szczegółową specyfikację pod adresem
http://www.w3.org/TR/html5/media-elements.html#mediaevents.

PRZEPIS DLA ZAAWANSOWANYCH


Tworzenie niestandardowych kontrolek
HTML5 udostępnia javascriptowe API mediów dla elementów video i audio. Jak pokazano w poprzednim
podrozdziale, obejmuje ono wiele metod i zdarzeń, dzięki którym możesz na przykład utworzyć własny
odtwarzacz wideo i niestandardowe kontrolki.
W tym przepisie bazujemy na kodzie wideo z listingu 7.4, do którego dodajemy przycisk Odtwórz/
Wstrzymaj, pasek postępu, regulację głośności, wyświetlacz czasu oraz przyciski szybkiego przewijania
do przodu i do tyłu. Jak wyjaśnimy później, przeglądarki mają swoje dziwactwa, więc efekt zastosowania
tego przepisu najlepiej jest oglądać w Operze albo Chrome, jak pokazano na rysunku 7.4.

Rysunek 7.4. Wideo z niestandardowymi kontrolkami oglądane w Operze 11

Podstawową funkcją potrzebną w przypadku własnych kontrolek jest przycisk Odtwórz, ale za pomocą
JavaScriptu i API mediów możesz zrobić znacznie więcej, co ilustruje kod z listingu 7.8.
1. Możesz opakować element video elementem <div id="video-wrapper"> i dodać <div
id="controls">, który będzie zawierał przyciski, suwaki i inne komponenty przydatne do
wyświetlenia kontrolek. Uwzględniliśmy ten fragment na początku sekcji body strony z tego
przepisu, aby był on bardziej przejrzysty, ale w praktyce powinieneś unikać umieszczania
kontrolek w zasadniczym kodzie, a zamiast tego tworzyć je „w locie” za pomocą JavaScriptu,
tak by widzieli je tylko użytkownicy z włączonym JavaScriptem.
2. Możesz zadeklarować element video jako obiekt, do którego można się odwoływać, a następnie
usunąć domyślne kontrolki przeglądarki przez pozbycie się atrybutu controls umieszczonego na
potrzeby użytkowników bez JavaScriptu. Przycisk Odtwórz pozostanie nieaktywny do momentu,
kiedy film będzie gotowy do odtwarzania.
Tworzenie niestandardowych kontrolek 177

3. Dla poprawy wydajności i „międzyprzeglądarkowej” przyjazności możesz zaczekać, aż film będzie


gotowy i będziesz mógł pobrać informacje, takie jak jego czas trwania. A kiedy film będzie gotowy
do odtworzenia, zostanie aktywowany przycisk Odtwórz.
4. Możesz dodawać funkcje i procedury nasłuchu do przycisków, innych kontrolek i wyświetlaczy.

Listing 7.8. Niestandardowe kontrolki wideo

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>7.8. Niestandardowe kontrolki wideo</title>
<style>
body {
font: bold .8em Arial, Helvetica, sans-serif;
}
video {
display: block;
}
#video-wrapper {
-moz-box-shadow: 0 0 20px rgba(0, 0, 0, .8);
-webkit-box-shadow: 0 0 20px rgba(0, 0, 0, .8);
box-shadow: 0 0 20px rgba(0, 0, 0, .8);
display: block;
margin: 20px auto;
overflow: hidden;
position: relative;
width: 568px;
}
#controls {
background: rgba(0, 0, 0, .3);
bottom: 0;
height: 30px;
left: 0;
padding: 35px 10px 10px;
position: absolute;
width: 548px;
z-index: 1;
}
button {
background: rgba(255, 255, 255, .7);
border: none; -moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
cursor: pointer;
padding: 5px
}
#play {
width: 70px;
}
#time, #duration {
color: #fff;
position: absolute;
top: 0;
}
#time {
left: 10px;
178 Rozdział 7. Osadzanie wideo w HTML5

}
#duration {
right: 10px;
text-align: right
}
input[type="range"] {
position: absolute;
}
#seekbar {
top: 8px;
width: 465px;
left: 50px;
}
#volume {
width: 50px;
}
#mute {
float: right;
width: 60px;
}
label[for="volume"] {
color: #fff;
float: right;
margin: 5px 55px 0 15px;
}
button.speed {
font-size: .8em
}
</style>
<script>
var video = null;
var seekbar = null;
var playBtn = null;

// inicjalizuj stronę
function init() {

// pobierz pojemnik wideo


video = document.getElementsByTagName('video')[0];

// odwołania do elementów input typu range


seekbar = document.getElementById('seekbar');
volume = document.getElementById('volume');

// ustaw obsługę zmiany pasków zakresu


seekbar.addEventListener('change',seek,false);
volume.addEventListener('change',changeVolume,false);

// odwołania do przycisków
playBtn = document.getElementById('play');
muteBtn = document.getElementById('mute');
rewindBtn = document.getElementById('rewind');
ffBtn = document.getElementById('ff');
fullscreenBtn = document.getElementById('fullscreen');

// ustaw obsługę klikania przycisków


playBtn.addEventListener('click',playPause,false);
muteBtn.addEventListener('click',mute,false);
rewindBtn.addEventListener('click',rewind,false);
Tworzenie niestandardowych kontrolek 179

ffBtn.addEventListener('click',fastforward,false);
fullscreenBtn.addEventListener('click',fullscreen,false);

// usuń domyślne kontrolki przeglądarki


video.removeAttribute('controls');

// inicjalizuj informacje odtwarzacza wideo


if (video.readyState > 0) {
var durationText = document.getElementById('duration');
durationText.innerHTML = (formatTime(video.duration));

var durationRounded = Math.round(video.duration);


seekbar.setAttribute('max', durationRounded);

playBtn.disabled = false;
seekbar.value = 0;
}

// nasłuchiwanie zdarzeń podczas odtwarzania wideo


video.addEventListener('timeupdate', function() {
var currentTime = document.getElementById('time');
currentTime.innerHTML = formatTime(video.currentTime);
seekbar.value = video.currentTime;
}, false);

video.addEventListener('ratechange', function() {
//mógłbyś alternatywnie wyświetlić aktualną prędkość odtwarzania
//console.log(video.playbackRate);
}, false);

video.addEventListener('play', function() {
playBtn.innerHTML = 'Wstrzymaj';
}, false);

video.addEventListener('pause', function() {
playBtn.innerHTML = 'Odtwórz';
}, false);

//wykrywa, kiedy wideo się kończy


video.addEventListener('ended', function(){
playBtn.innerHTML = 'Od nowa';
}, false);

function playPause() {
if (ifPlaying()) {
video.pause();
playBtn.innerHTML = 'Odtwórz';
} else {
video.play();
playBtn.innerHTML = 'Wstrzymaj';
}
};

//przełącz stan wyciszenia wideo


function mute(){
var muteBtn = document.getElementById('mute');
if (!video.muted){
180 Rozdział 7. Osadzanie wideo w HTML5

video.muted = true;
muteBtn.innerHTML = 'Dźwięk';
}
else {
video.muted = false;
muteBtn.innerHTML = 'Wycisz';
}
}

//zmień głośność
function changeVolume() {
video.volume = volume.value;
}

//kontrolki paska przewijania


function seek(){
video.currentTime = seekbar.value;
}

//przewiń wideo do przodu


function fastforward() {
video.playbackRate = video.playbackRate + 2;
}

//przewiń wideo do tyłu


function rewind() {
video.playbackRate = video.playbackRate - 2;
}

//przejdź do trybu pełnoekranowego (tylko webkit)


function fullscreen() {
video.webkitEnterFullscreen()
}

//sprawdź, czy wideo jest odtwarzane


function ifPlaying() {
if(video.paused || video.ended) {
return false;
} else {
return true;
}
};

//sformatuj czas do przyjaznej i czytelnej postaci


function formatTime(seconds) {
seconds = Math.round(seconds);
minutes = Math.floor(seconds / 60);
minutes = (minutes >= 10) ? minutes : '0' + minutes;
seconds = Math.floor(seconds % 60);
seconds = (seconds >= 10) ? seconds : '0' + seconds;
return minutes + ':' + seconds;
}

// inicjalizuj stronę po jej załadowaniu


window.addEventListener('load',init,false);

</script>
</head>
<body>
Tworzenie niestandardowych kontrolek 181

<div id="video-wrapper">
<div id="controls">
<button id="play">Odtwórz</button>
<p id="time">0:00</p>
<p id="duration">0:00</p>
<label for="volume"> Głośność:<input id="volume" name="volume" type="range" min="0" max="1"
´step="0.1" required /></label>
<button id="mute">Wycisz</button>
<input id="seekbar" name="seekbar" type="range" min="0" max="1" step="0.1" required />
<button id="rewind" class="speed">Do tyłu</button>
<button id="ff" class="speed">Do przodu</button>
<button id="fullscreen">Pełny ekran</button>
</div>

<video width="568" height="320" controls>

<!-- wideo dla Safari i IE9; ze względu na iPada MP4 musi być pierwszym formatem -->
<source src="cablecar.mp4" type="video/mp4" />

<!-- wideo dla Chrome, Firefoksa i Opery -->


<source src="cablecar.webm" type="video/webm" />
<source src="cablecar.ogv" type="video/ogg" />

</video>
</div>
</body>
</html>

Na początku kodu umieściliśmy trochę CSS-a dotyczącego pewnych podstawowych stylów i rozmieszczenia
przycisków. W tym przykładzie nie przesadzaliśmy z obstylowaniem, ale za pomocą CSS-a i API JavaScriptu
mógłbyś stworzyć właściwie każdy układ odtwarzacza wideo, jaki byś tylko chciał.
Przejdźmy teraz do JavaScriptu. Najpierw należy powiązać zmienne z niektórymi elementami: samym
wideo, paskiem przewijania i przyciskiem Odtwórz. Dla wygody użytkowników bez obsługi JavaScriptu
dołączyliśmy do elementu video atrybut controls, który następnie za pomocą JavaScriptu usuwamy, gdyż
nie chcemy, żeby domyślne kontrolki były widoczne.
W kolejnym fragmencie kodu mamy do czynienia z bardzo istotnym procesem. Zanim będzie można
rozpocząć odtwarzanie filmu, przeglądarka musi zdobyć wystarczającą ilość związanych z nim informacji.
Niektóre przeglądarki nie potrafią na przykład pozyskać długości filmu, ponieważ nadal pobierają jego części.
Zatem w przypadku dużego pliku wideo będziesz być może musiał trochę poczekać, zanim przeglądarka
będzie gotowa do jego odtworzenia. W niniejszym przepisie wykrywamy wartość readyState, która może
przyjąć jedną z pięciu wartości:
„ have_nothing (0),
„ have_metadata (1),
„ have_current_data (2),
„ have_future_data (3),
„ have_enough_data (4).

Wartość have_nothing oznacza, że przeglądarka nie posiada żadnych informacji na temat filmu. Pozostałe
wartości wskazują, że przeglądarka posiada jakieś informacje na jego temat, w tym czas trwania, wysokość
i szerokość, aktualną pozycję oraz następną ramkę. W bieżącym przepisie oczekujemy momentu, w którym
182 Rozdział 7. Osadzanie wideo w HTML5

wartość readyState będzie większa od 0; kiedy tak się stanie, czas trwania zostanie zwrócony, a jego wartość
na ekranie — zaktualizowana. Aktywowany zostaje również przycisk Odtwórz, dzięki czemu użytkownik
może uruchomić film. W niniejszym przepisie, kiedy film jest gotowy, niewiele się już w kodzie dzieje, ale
mógłby to być moment, w którym tworzone są kontrolki wideo, pokazywane lub ukrywane ładowanie
grafiki, a może nawet wyświetlany film.
Alternatywnie mógłbyś użyć obiektu nasłuchującego zdarzenia loadeddata, które nastąpi, gdy przeglądarka
będzie miała na tyle dużo informacji na temat filmu, aby umożliwić rozpoczęcie odtwarzania:
video.addEventListener("loadeddata", function(){//zrób, co trzeba}, false);

Znasz już teraz czas trwania filmu, więc przy użyciu video.duration możesz uzyskać jego całkowitą
długość, którą przypiszesz do paska przewijania input[type="range"], tak aby miał on punkt końcowy.
Następnie należy umieścić punkt początkowy suwaka na początku (zero sekund). Niektóre zdarzenia
addEventListener są wykorzystywane do wykrycia, czy czas odtwarzania filmu wideo został zaktualizowany
(timeupdate), a jeśli tak się stało, aktualizowane są ekran odtwarzania i pasek przewijania.
Nawet jeśli usunąłeś domyślne kontrolki wideo i utworzyłeś własne przyciski, także Odtwórz i Wycisz,
użytkownik może, w zależności od przeglądarki, uzyskać po kliknięciu wideo prawym przyciskiem myszy
dostęp do podstawowych opcji: odtwarzania i zatrzymania, pokazania kontrolek, wyciszenia, zapisu wideo
i innych. Tych domyślnych ustawień przeglądarki nie można usunąć ani zmienić, ale odpowiednie zdarzenia
można śledzić — w przepisie znajduje się fragment kodu, w którym pobierana jest informacja o rozpoczęciu
lub zatrzymaniu odtwarzania, a w odpowiedzi zostaje zmieniony tekst przycisku.
Aby zmienić tekst przycisku Odtwórz, warto potwierdzić moment zakończenia filmu — to o tyle przydatna
informacja, że wraz z zakończeniem filmu mogłyby mieć miejsce kolejne działania, takie jak rozpoczęcie
kolejnego filmu z listy odtwarzania. Zakończenie filmu możesz potwierdzić przez wykrycie przy użyciu obiektu
nasłuchującego zdarzenia ended:
video.addEventListener("ended", function(){//zrób, co trzeba}, false);

W wywołaniach addEventListener znajdują się różne funkcje używane do wywołania innych funkcji wideo,
takich jak sprawdzenie, czy wideo jest odtwarzane, ustawienie głośności (video.volume = volume.value),
odtwarzanie wideo w trybie pełnego ekranu (video.webkitEnterFullscreen() — działa tylko w przeglądarkach
WebKit) czy sprawdzenie tempa odtwarzania (playbackRate). Została również dodana funkcjonalność
odtwarzania, aby użytkownik mógł sterować pozycją i kierunkiem wideo — dzięki paskowi przewijania
użytkownik może przejść do dowolnego miejsca na osi czasu (video.currentTime = seekbar.value), dostępne
są również przyciski przewijania do przodu i do tyłu.
Przeglądarki mają jednak swoje dziwactwa, jeśli chodzi o pracę z niestandardowymi kontrolkami:
„ W chwili pisania tej książki jedynym sposobem, aby wyświetlić film na pełnym ekranie, było użycie
funkcji webkitEnterFullscreen, która działała wyłącznie w przeglądarce Safari. Sądząc po nazwie,
nie jest to jeszcze standard odtwarzania filmów na pełnym ekranie i taki standard może się nigdy
nie pojawić w specyfikacji. Producenci przeglądarek wiedzą jednak, czego pragną użytkownicy,
więc jest bardzo prawdopodobne, że w przyszłości pojawią się pewne niestandardowe rozwiązania
właściwe dla poszczególnych przeglądarek. Istnieją także inne sposoby odtwarzania wideo na pełnym
ekranie, wykorzystujące kombinację JavaScriptu i CSS-a, a niektóre przeglądarki dodatkowo
umożliwiają przejście do trybu pełnoekranowego po kliknięciu prawym przyciskiem myszy samego
filmu wideo.
Podsumowanie 183

„ Pasek przewijania <input type="range"> obsługują obecnie tylko Safari, Chrome i Opera, przy
czym tylko w Operze może on posiadać przezroczyste tło — w Chrome prezentuje się raczej
brzydko, a w Safari po prostu nie jest widoczny. Firefox i IE9 wyświetlają domyślnie pole tekstowe,
w którym jest prezentowany aktualizujący się czas.
„ Przycisk przewijania do przodu w IE9 nie działa płynnie, a w Firefoksie w ogóle nie działa. Przycisk
przewijania do tyłu nie działa jeszcze w żadnej przeglądarce.

Z powodu powyższych rozbieżności w przeglądarkach — dotyczących zarówno wyświetlania kontrolek,


jak i działania API — warto rozważyć użycie innych kontrolek. Na przykład kontrolki wideo widoczne
na rysunku 7.5 zostały utworzone przy użyciu suwaka jQuery. Dostępne są także inne odtwarzacze wideo,
na przykład na stronach http://sublimevideo.net oraz http://videojs.com.

Rysunek 7.5. Wideo z niestandardowymi kontrolkami jQuery oglądane w Firefoksie 4

Podsumowanie
W tym rozdziale przeanalizowałeś różne kwestie związane z elementem video, takie jak jego obsługa
w przeglądarkach, ale największy problem dotyczy obsługi kodeków, i to właśnie ten aspekt szczególnie
warto śledzić.
Oprócz informacji o kodekach zdobyłeś również wiedzę na temat sposobów natywnego odtwarzania
filmów wideo we wszystkich aktualnych wersjach najpopularniejszych przeglądarek, bez konieczności
korzystania z zewnętrznych wtyczek, takich jak Flash. W przypadku starszych przeglądarek, które nie
obsługują elementu video, możesz użyć awaryjnie łącza albo osadzonego pliku Flash. Stworzyłeś również
odtwarzacz wideo z napisami, używając najnowszego standardu plików WebVTT, choć jest to raczkujący
jeszcze standard, więc należy na bieżąco śledzić wszelkie zmiany i sposoby jego zastosowania.
Na koniec omówiliśmy kilka składników API mediów, umożliwiającego tworzenie własnych kontrolek,
które możesz formatować za pomocą CSS-a.
184 Rozdział 7. Osadzanie wideo w HTML5
8
Osadzanie dźwięku w HTML5

A nalogicznie do elementu video, nowy element audio pozwala na osadzenie pliku dźwiękowego na stronie
bez potrzeby używania dodatkowych wtyczek, takich jak Flash. W nowoczesnych przeglądarkach
internetowych, włącznie z przeglądarkami dostępnymi na wielu urządzeniach przenośnych, dźwięk jest
odtwarzany natywnie — nie muszą one już polegać na zewnętrznych wtyczkach.
W tym rozdziale omówimy podstawowe zagadnienia związane z elementem audio, a następnie przyjrzymy
się sposobom rozwiązywania problemów pojawiających się w różnych przeglądarkach. Następnie poznasz
API, dzięki któremu możesz stworzyć swój własny odtwarzacz dźwięku.

PRZEPIS DLA POCZĄTKUJĄCYCH


Umieszczanie dźwięku za pomocą elementu audio
Prosty przykład umieszczenia dźwięku na stronie przy użyciu nowego elementu audio został przedstawiony
na listingu 8.1. Dodany został atrybut controls, bez którego na interfejsie użytkownika nic by się nie wyświetliło.

Listing 8.1. Prosty przykład elementu audio

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>8.1. Prosty przykład audio</title>
</head>
<body>
<audio src="music.mp3" controls />
</body>
</html>

Kod jest bardzo prosty, ale tak jak w przypadku elementu video, mogą się pojawić pewne problemy.
Jeśli kod z listingu 8.1 spróbujesz zastosować z plikiem .mp3 i nie usłyszysz dźwięku, przyczyną może być
problem z kodekami.
Męki z kodekami opisaliśmy bardziej szczegółowo w rozdziale 7., a chociaż informacje tam podane
dotyczą konkretnie typów plików wideo, sytuacja z typami plików dźwiękowych jest analogiczna.
186 Rozdział 8. Osadzanie dźwięku w HTML5

Dwa główne kodeki audio to Ogg Vorbis (.ogg) i MP3 (.mp3). Możesz również pomyśleć o formacie WAV
(.wav), ale pliki tego typu są zazwyczaj duże, przez co nie nadają się do ładowania przez sieć, a dodatkowo
nie obsługują metadanych, takich jak wykonawca i tytuł. W tej książce skoncentrujemy się więc na obu
głównych kodekach. Chociaż format MP3 może być uważany niemal za standardowy typ pliku, jest on
częścią grupy MPEG4/H.264, a więc jest typem „zamkniętym” i komercyjnym. Z drugiej strony Ogg uważa
się za „wolny” i „otwarty”. Zdania co do tego, który typ pliku daje dźwięk o lepszej jakości, są podzielone,
ale podstawową kwestię stanowi zróżnicowana obsługa w przeglądarkach. W tabeli 8.1 przedstawiono aktualną
sytuację dotyczącą odtwarzania w przeglądarkach dźwięku w różnych formatach.

Tabela 8.1. Typy plików i ich obsługa w przeglądarkach

Internet
Kodek Android Chrome Firefox iPhone Opera Safari
Explorer
Ogg Vorbis - 13+ 4+ - - 11+ -
MP3 2.3 13+ - 9+ 4+ - 5+
WAV - - - - - - -

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Umieszczanie dźwięku we wszystkich przeglądarkach
Tak jak w przypadku wideo w rozdziale 7., chciałbyś, aby dźwięk był odtwarzany i obsługiwany we wszystkich
najpopularniejszych przeglądarkach. W tym celu możesz wykorzystać zagnieżdżony w elemencie audio
element source, wskazujący po kolei różne pliki dźwiękowe, a przeglądarka wybierze, który z nich może
odtworzyć. Listing 8.2 prezentuje kod potrzebny do udostępnienia najpopularniejszym przeglądarkom pliku
dźwiękowego, który mogą one odtworzyć.

Listing 8.2. Zestaw plików dźwiękowych

<audio controls>
<source src="music.mp3" type="audio/mp3" />
<source src="music.ogg" type="audio/ogg" />
</audio>

Element audio nie wymaga określenia wysokości lub szerokości, ponieważ domyślnie każda przeglądarka
posiada swój własny odtwarzacz dźwięku, ale możesz wskazać wysokość i szerokość przy użyciu CSS-a:
audio {display: block; width: 90px; height: 28px;}

Dodawanie zastępczej treści dla starszych przeglądarek


Po elementach source możesz umieścić dodatkowy kod dla przeglądarek, które nie mogą odtworzyć dźwięku
natywnie — przede wszystkim mówimy o Internet Explorerze 6, 7 i 8. Podobnie jak w przypadku elementu
video, istnieje kilka możliwości. Możesz przechowywać plik dźwiękowy na stronie takiej jak http://soundcloud.
com i używać pochodzącego z niej kodu służącego do osadzania pliku, aby dostarczyć dźwięk, ale w celu
dostarczenia zawartości dźwiękowej do starszych przeglądarek możesz także użyć programu Flash Player.
Na listingu 8.3 używamy JW Playera, bardzo popularnego i łatwego do skonfigurowania odtwarzacza
Nowe atrybuty elementu audio 187

multimedialnego, ale dostępnych jest wiele innych odtwarzaczy. Mógłbyś też zresztą utworzyć własny
odtwarzacz, a niezbędne pliki — w naszym przypadku player.swf i swfobject.js — bez problemu znajdziesz
w internecie.

Listing 8.3. Użycie JW Playera

<script type="text/javascript" src="swfobject.js"></script>


<div id="mediaplayer"></div>
<script>
var so = new SWFObject('player.swf','playerID','480','24','9');
so.addParam('allowfullscreen','true');
so.addParam('allowscriptaccess','always');
so.addVariable('file', 'music.mp3');
so.write('mediaplayer');
</script>

Tylko starsze przeglądarki pobierają pliki swfobject.swf oraz player.swf i odtwarzają plik dźwiękowy
w programie Flash Player.
Listing 8.4 przedstawia cały kod. Zapewnia on odtwarzanie dźwięku we wszystkich najpopularniejszych
przeglądarkach — czy to za pomocą natywnego elementu audio, czy to, w razie potrzeby, przy użyciu
zastępczego odtwarzacza Flash Player.

Listing 8.4. Odtwarzanie dźwięku w różnych przeglądarkach

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>8.4. Odtwarzanie dźwięku w różnych przeglądarkach</title>
</head>
<body>
<audio controls>
<source src="music.mp3" type="audio/mp3" />
<source src="music.ogg" type="audio/ogg" />
<script type="text/javascript" src="swfobject.js"></script>
<div id="mediaplayer"></div>
<script>
var so = new SWFObject('player.swf','playerID','480','24','9');
so.addParam('allowfullscreen','true');
so.addParam('allowscriptaccess','always');
so.addVariable('file', 'music.mp3');
so.write('mediaplayer');
</script>
</audio>
<p>Pobierz plik dźwiękowy: <a href="music.mp3">plik MP3, 3 MB</a>; <a href="music.ogg">plik Ogg, 3 MB</a></p>
</body>
</html>

Nowe atrybuty elementu audio


Omówimy teraz kilka nowych atrybutów elementu audio.
188 Rozdział 8. Osadzanie dźwięku w HTML5

Atrybut src
Listing 8.1 z początku rozdziału pokazuje prosty przykład elementu audio i zawiera atrybut src. Użycie src
oznacza jednak, że jesteś ograniczony do jednego pliku dźwiękowego. Kiedyś, w idealnym świecie, będzie
to wszystko, czego będziesz potrzebować, jednak w chwili obecnej — ze względu na problemy z kodekami
i przeglądarkami — sposób ten jest w zasadzie bezużyteczny. Zamiast korzystać z src, rozważ zastosowanie
w obrębie elementu audio elementu source (jak na listingu 8.4), tak abyś mógł odtwarzać dźwięk w różnych
przeglądarkach.

Atrybut preload
Jeśli plik dźwiękowy osadzony na Twojej stronie ma dużą objętość albo jesteś pewien, że użytkownik będzie
chciał go słuchać, możesz nakazać przeglądarce rozpoczęcie pobierania pliku po wczytaniu strony. Atrybut
preload może przyjąć jedną z trzech wartości:
preload="auto"

Wartość auto (możesz też po prostu pozostawić samo preload) informuje przeglądarkę, aby pobrała plik
dźwiękowy po załadowaniu strony.
preload="none"

Wartość preload="none" mówi przeglądarce, aby nie pobierała zawartości dźwiękowej. Dopiero kiedy
użytkownik uruchomi odtwarzanie dźwięku, przeglądarka zacznie go pobierać.
preload="metadata"

Atrybut metadata pobiera informacje (metadane) o ścieżce dźwiękowej, w tym jej czas trwania i listę
utworów. Przeglądarka nie powinna pobrać samej zawartości dźwiękowej, dopóki użytkownik jej nie
uruchomi.

Atrybut loop
Jeśli element audio posiada atrybut loop, po zakończeniu odtwarzania rozpocznie się ono od początku.
Atrybut ten jest atrybutem logicznym — albo jest włączony, albo wyłączony.

Atrybut autoplay
Atrybut logiczny autoplay wymusza na przeglądarce rozpoczęcie pobierania i odtwarzania dźwięku po
wczytaniu strony. W ten sposób odtwarzanych jest na przykład wiele reklam. Istnieją pewne obawy związane
z dostępnością i łatwością wykorzystania odtwarzanych automatycznie dźwięków, nie wspominając już o tym,
jak dźwięki mogą być irytujące, ale atrybut autoplay znajduje się w specyfikacji ze względu na to, że pozwala
w ustandaryzowany sposób wymusić automatyczne odtwarzanie dźwięku, gdybyś naprawdę tego potrzebował.
Nie musisz uciekać się do różnych javascriptowych sztuczek, aby osiągnąć ten efekt. Zauważ, że dostępne
są też różne rozszerzenia przeglądarek wyłączające autoplay.

Uwaga
W systemie iOS atrybuty autoplay i preload zostały celowo wyłączone ze względu na potencjalne
koszty i problemy związane z prędkością wczytywania u użytkownika. Żadne dane nie są pobierane,
dopóki użytkownik nie zdecyduje się ręcznie uruchomić dźwięku. Istnieje kilka technik (a raczej
sztuczek) pozwalających na obejście tej blokady, ponieważ jednak są to techniki nieoficjalne,
nie powinny być wykorzystywane i nie zostały omówione w tej książce.
API mediów 189

Atrybut controls
Atrybut controls dodaje do pliku dźwiękowego domyślne, właściwe dla danej przeglądarki kontrolki
zawierające przycisk odtwarzania/pauzy, pasek przewijania, informację o czasie trwania i czasie od początku
utworu oraz kontrolki sterowania głośnością. Oto przykład:
<audio controls>

</audio>

Jest to atrybut logiczny, więc albo go dołączasz, albo nie. Jeśli nie zostanie dołączony, nic nie zostanie
wyświetlone i wówczas oczywiście użytkownik nie będzie mógł sterować dźwiękiem. Zdajesz sobie chyba
zatem sprawę, jak ważne jest, aby nie korzystać z atrybutu autoplay bez równoczesnego użycia atrybutu
controls.
Jeśli chcesz, możesz wykorzystać API do utworzenia własnych kontrolek rozpoczynania i wstrzymywania
odtwarzania — podyskutujemy o tym w dalszej części tego rozdziału. Rysunek 8.1 pokazuje domyślne
kontrolki w najpopularniejszych przeglądarkach.

Rysunek 8.1. Domyślne kontrolki w Chrome (na górze), Firefoksie,


Internet Explorerze 9, Operze i Safari

Wskazówka
Co dziwne, jeśli w Firefoksie nie masz włączonej obsługi JavaScriptu, kontrolki nie są wyświetlane.
Inne przeglądarki wyświetlają kontrolki poprawnie bez pomocy JavaScriptu.

Darmowe pliki audio do testowania


Prawdopodobnie do celów testowych będziesz potrzebował jakichś darmowych plików dźwiękowych.
Istnieją dziesiątki możliwości. My wykorzystaliśmy pliki z takich witryn jak http://beatstorm.com,
http://freesoundtrackmusic.com, http://www.vocaldownloads.com i http://www.freesound.org.
Pliki mają różne formaty i, jak już wiesz, aby odtwarzać je w różnych przeglądarkach, będziesz je
musiał przekonwertować. Sugerujemy wypróbowanie strony http://media.io albo aplikacji Free MP3/
WMA/OGG Converter, dostępnej na stronie http://download.cnet.com/Free-MP3-WMA-OGG-Converter/
3000-2140_4-10793572.html.

API mediów
HTML5 udostępnia rozwinięte i ekscytujące API dla multimediów. Korzystając z tego API i JavaScriptu,
możesz wpływać na odtwarzanie plików dźwiękowych na stronach internetowych. Poniżej znajduje się lista
dostępnych zdarzeń API:
190 Rozdział 8. Osadzanie dźwięku w HTML5

abort loadstart
canplay pause
canplaythrough play
canshowcurrentframe playing
dataunavailable progress
durationchange ratechange
emptied seeked
empty seeking
ended suspend
error timeupdate
loadeddata volumechange
loadedmetadata waiting

Dostępnych jest również wiele właściwości mediów:


audioTracks muted
autoplay networkState
buffered paused
controls preload
controller played
currentSrc playbackRate
currentTime readyState
defaultMuted seekable
defaultPlaybackRate seeking
duration startOffsetTime
ended src
error textTracks
initialTime videoTracks
loop volume
mediaGroup

Nie wszystkie z powyższych zdarzeń i właściwości są już dostępne, ale większości, w tym tych istotnych,
które pozwalają tworzyć własne kontrolki odtwarzacza dźwięku, można już używać. Nie będziemy tu
opisywać wszystkich możliwości API — ograniczymy się do tych, które są istotne dla kolejnych przepisów.
Jeśli jednak jesteś zainteresowany zdobyciem większej wiedzy na temat tego API, polecamy materiały
na stronie http://www.w3.org/2010/05/video/mediaevents.html (to demonstracja wideo, ale API dotyczy
również odtwarzania dźwięku) oraz bardzo szczegółową specyfikację pod adresem http://www.w3.org/TR/
html5/media-elements.html#mediaevents.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Tworzenie miksera
Dotychczas dowiedziałeś się, jak uzyskać natywne odtwarzanie dźwięku w nowoczesnych przeglądarkach.
W tym przepisie utworzysz mikser, dzięki czemu przetestujesz, jak przeglądarka radzi sobie z obsługą
wielu jednocześnie odtwarzanych elementów audio. To proste zadanie, odpowiednie do rozpoczęcia pracy
z dźwiękiem i API.
W tym przepisie wykorzystujemy pliki .mp3 dla Safari i Internet Explorera oraz pliki .ogg dla Chrome,
Firefoksa i Opery. Przeglądarki mają swoje dziwactwa, więc zalecamy używanie Chrome i Opery. Starszymi
wersjami Internet Explorera zupełnie nie będziemy się interesować.
Tworzenie miksera 191

Rozpoczynamy od sześciu elementów dźwiękowych na liście, choć teoretycznie moglibyśmy na bieżąco


ładować kolejne pliki. Do opracowania układu i nadania podstawowych stylów zastosujemy nieco CSS-a;
odpowiednia klasa przyda się także w sytuacji, gdy dźwięk nie jest odtwarzany. Dla każdego elementu
audio dostępne jest zdarzenie onclick do przełączania stanu odtwarzania pliku. Stronę możesz zobaczyć
na rysunku 8.2, a kod zawarty jest na listingu 8.5.

Rysunek 8.2. Działający mikser oglądany w Firefoksie

Listing 8.5. Tworzenie miksera

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>8.5. Mikser</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script>
$(function(){
$('audio').each(function(index){
$(this).removeAttr("controls");
$(this).addClass('stopped');
var currentTime = $(this).next('div');
currentTime.html("")
$(this).click(function() {
if (this.paused == false) {
this.pause();
$(this).addClass('stopped');
this.currentTime = 0;
} else {
this.play();
$(this).removeClass('stopped');
}
});
this.addEventListener('timeupdate', function() {
currentTime.html(formatTime(this.currentTime));
}, false);
});
});
192 Rozdział 8. Osadzanie dźwięku w HTML5

function formatTime(seconds) {
seconds = Math.round(seconds);
minutes = Math.floor(seconds / 60);
minutes = (minutes >= 10) ? minutes : '0' + minutes;
seconds = Math.floor(seconds % 60);
seconds = (seconds >= 10) ? seconds : '0' + seconds;
return minutes + ':' + seconds;
}
</script>
<style>
* {
padding: 0;
margin: 0;
}

ul {
list-style-type: none;
margin: 50px auto;
width: 205px;
}

li {
float: left;
margin: 0 0 5px 5px;
}

li:nth-child(odd) {
clear: both;
margin-left: 0;
}

p {
clear: both;
}

audio {
background: url(images/stop.png) center center no-repeat #ccc;
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
border-radius: 50px;
-moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
-webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
cursor: pointer;
display: block;
height: 100px;
margin: 0 0 5px;
width: 100px;
}

audio.stopped {
background: url(images/play.png) center center no-repeat #fff;
}

li div {
clear: both;
text-align: center;
}
Tworzenie miksera 193

</style>

</head>
<body>
<ul>
<li>
<audio controls loop>
<source src="guitar.mp3" type="audio/mp3" />
<source src="guitar.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
<li>
<audio controls loop>
<source src="beat.mp3" type="audio/mp3" />
<source src="beat.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
<li>
<audio controls loop>
<source src="turntable.mp3" type="audio/mp3" />
<source src="turntable.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
<li>
<audio controls loop>
<source src="clap.mp3" type="audio/mp3" />
<source src="clap.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
<li>
<audio controls loop>
<source src="boxingball.mp3" type="audio/mp3" />
<source src="boxingball.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
<li>
<audio controls loop>
<source src="synth6.mp3" type="audio/mp3" />
<source src="synth6.ogg" type="audio/ogg" />
</audio>
<div></div>
</li>
</ul>
<p>Utwory z <a href="http://beatstorm.com">beatstorm.com</a>; <a
´href="http://www.vocaldownloads.com">vocaldownloads.com</a>; <a
´href="http://www.freesound.org">freesound.org</a></p>
</body>
</html>

W pierwszej kolejności ukrywane są domyślne kontrolki i dzięki zastosowaniu CSS-a wyświetlany jest
przycisk Odtwórz. Kiedy użytkownik kliknie element audio, sprawdzamy, czy plik był wcześniej wstrzymany.
Jeśli tak było, za pomocą metody play dźwięk zostaje uruchomiony. Jeśli plik był odtwarzany, zostaje
194 Rozdział 8. Osadzanie dźwięku w HTML5

wstrzymany za pomocą metody pause. Podczas odtwarzania lub wstrzymywania przełączana jest klasa,
która obrazuje różne działania przycisków. Kiedy dźwięk jest wstrzymywany, odtwarzanie pliku zostaje
faktycznie zatrzymane, gdyż jego atrybut currentTime przyjmuje wartość 0. Natomiast do pokazania w trakcie
odtwarzania czasu, jaki upłynął od początku utworu, wykorzystywane jest zdarzenie nasłuchu timeupdate.

Dziwactwa przeglądarek dotyczące miksera


Przy realizacji tego przepisu możesz się natknąć na pewne nietypowe zachowania przeglądarek.
Chociaż JavaScript jest obsługiwany w IE9 i Safari, w tych przeglądarkach przykład nie zadziała,
ponieważ nie obsługują one jeszcze formatowania elementu audio. Kod powinien zostać zinterpretowany
poprawnie dopiero wtedy, gdy usuniesz wiersz, który ukrywa atrybut controls, choć nawet wówczas
przeglądarki wcale nie muszą reagować właściwie. Możesz także sformatować element li, tak aby
zawierał ikony odtwarzania/wstrzymania.

PRZEPIS DLA ZAAWANSOWANYCH


Dodawanie internetowego radia
Dotychczas w tym rozdziale zajmowaliśmy się odtwarzaniem „fizycznych” plików (.mp3 i .ogg). A co z treścią
strumieniowaną? Aby móc odtwarzać muzykę strumieniowaną, potrzebowaliśmy dotychczas Flasha, ale
obecnie można to zrobić natywnie. W tym przepisie użyjemy listy JSON publicznie dostępnych strumieni
radiowych. Użytkownik może wybrać z rozwijanej listy strumień, którego odtwarzanie zostanie za pomocą
API rozpoczęte. Fajnie, prawda? Oto radio internetowe w przeglądarce, bez żadnych wtyczek. Podgląd
odtwarzacza jest widoczny na rysunku 8.3, a kod do tego przepisu znajduje się na listingu 8.6.

Rysunek 8.3. Odtwarzacz internetowego radia w HTML5 oglądany w Chrome

Listing 8.6. Odtwarzacz radia internetowego

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>8.6. Odtwarzacz radia internetowego</title>
Dodawanie internetowego radia 195

<script>
// zmienne lokalne elementów
var radioPlayer;
var radioStatus;
var radioControls;
var volumeControl;
var buttonPlayPause;

// zdefiniuj naszą listę JSON stacji


var stations = { "entries":[
{"name":"Radio Roxy",
"url":"http://bialystok.radio.pionier.net.pl:8000/pl/tuba8-1.mp3",
"type":"mp3"},
{"name":"Radio BLUE FM",
"url":"http://stream4.radioagora.pl:8000/bluefm.ogg",
"type":"ogg"},
{"name":"Złote Przeboje",
"url":"http://bialystok.radio.pionier.net.pl:8000/pl/zloteprzeboje.ogg",
"type":"ogg"},
{"name":"Radio TOK FM",
"url":"http://wroclaw.radio.pionier.net.pl:8000/pl/radiotok.ogg",
"type":"ogg"},
{"name":"Radio Jazz FM",
"url":"http://radiojazzfm.yellow-lab.pl:8000/radiojazzfm-hi.mp3",
"type":"mp3"},
{"name":"Radio Eska Poznań",
"url":"http://sask1-4.radio.pionier.net.pl:8000/pl/eska-poznan.mp3",
"type":"mp3"},
{"name":"Radio WAWA",
"url":"http://bialystok.radio.pionier.net.pl:8000/pl/wawa.mp3",
"type":"mp3"},
{"name":"Radio Merkury Poznań",
"url":"http://gdansk.radio.pionier.net.pl:8000/pl/merkury.ogg",
"type":"ogg"},
]}

// funkcja inicjalizująca
function init() {

// ustaw odwołanie do elementu audio


radioPlayer = document.getElementById('audioPlayer');

// ustaw obsługę błędów


radioPlayer.onerror = function(evt) {
switch (radioPlayer.error.code) {
case radioPlayer.error.MEDIA_ERR_ABORTED:
alert('Odtwarzanie zostało przerwane.');
break;
case radioPlayer.error.MEDIA_ERR_NETWORK:
alert('Wystąpił błąd sieciowy.');
break;
case radioPlayer.error.MEDIA_ERR_DECODE:
alert('Wystąpił błąd podczas dekodowania strumienia.');
break;
case radioPlayer.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
alert('Udostępniony zasób medialny nie jest odpowiedni.');
break;
default:
alert('Wystąpił nieznany błąd: '+radioPlayer.error.code+'.');
196 Rozdział 8. Osadzanie dźwięku w HTML5

break;
}
}

// ustaw procedurę nasłuchu zdarzenia durationchange


radioPlayer.addEventListener('durationchange', streamPlaying, false);

// ustaw odwołania do elementów


radioStatus = document.getElementById('radioStatus');
radioControls = document.getElementById('radioControls');
volumeControl = document.getElementById('volumeControl');
buttonPlayPause = document.getElementById('buttonPlayPause');

// ustaw domyślny poziom głośności


setVolume(0.7);

// załaduj listę stacji


loadStations();

// załaduj stacje ze zmiennej JSON


function loadStations() {

// odwołanie do elementu select z listą stacji


var stationList = document.getElementById('stationList');

// przejdź w pętli przez stacje JSON i utwórz listę


for(i=0;i<stations.entries.length;i++) {
// utwórz element option z tekstem i wartością
var newOption = document.createElement('option');
newOption.text = stations.entries[i].name + ' ('+stations.entries[i].type+')';
newOption.value = i;

// dodaj nowy element option do listy


try {
stationList.add(newOption, null);
} catch(ex) {
// tylko IE
stationList.add(newOption);
}
}
}

// ustaw wybraną stację


function setStation() {

// uaktualnij status
radioStatus.innerHTML = 'Buforowanie...';

// ustaw źródło i typ elementu audio


var selStationList = document.getElementById('stationList');
radioPlayer.src = stations.entries[selStationList.selectedIndex].url;
radioPlayer.type = 'audio/' +stations.entries[selStationList.selectedIndex].type;

// wyświetl użytkownikowi wybraną stację


var currentStation = document.getElementById('currentStation');
currentStation.innerHTML = stations.entries[selStationList.selectedIndex].name;

// nakaż elementowi audio odtwarzać


Dodawanie internetowego radia 197

radioPlayer.play();
}

// strumień jest aktualnie odtwarzany


function streamPlaying() {

// aktualizuj wyświetlacz i pokaż kontrolki odtwarzacza


radioStatus.innerHTML = 'Teraz odtwarzam...';
buttonPlayPause.innerHTML = 'Wstrzymaj';
radioControls.style.visibility = 'visible';
}

// funkcje Odtwórz/Wstrzymaj
// obsługa kliknięcia przycisku Odtwórz/Wstrzymaj
function playPauseClicked() {
// sprawdź, czy odtwarzanie trwa, czy jest wstrzymane
if (radioPlayer.ended || radioPlayer.paused) {
// odtwarzacz jest wstrzymany; teraz odtwarzaj
playerPlay();
} else {
// odtwarzacz w trybie odtwarzania; teraz wstrzymaj
playerPause();
}
}

// uruchom ponownie odtwarzanie


function playerPlay() {

// aktualizuj status
buttonPlayPause.innerHTML = 'Wstrzymaj';
radioStatus.innerHTML = 'Teraz odtwarzam...';
// ponowne uruchomienie odtwarzania
radioPlayer.play();
}

// wstrzymaj odtwarzanie
function playerPause() {

// aktualizuj status
buttonPlayPause.innerHTML = 'Odtwórz';
radioStatus.innerHTML = 'Wstrzymano';

// wstrzymaj odtwarzanie
radioPlayer.pause();
}

// funkcje regulacji głośności


// ustaw poziom głośności
function setVolume(newVolume)
{
// ustaw poziom głośności
radioPlayer.volume = newVolume;

// aktualizuj rozmiar paska głośności


wrapper = document.getElementById('volume_background');
wrapper_width = wrapper.offsetWidth;
newWidth = wrapper_width*newVolume;

volume_bar = document.getElementById('volume_bar');
volume_bar.innerHTML = parseInt(newVolume*100) + '%';
198 Rozdział 8. Osadzanie dźwięku w HTML5

volume_bar.style.width=newWidth+'px';
}

// obsługa kliknięcia paska głośności


function volumeChangeClicked(event)
{
// uzyskaj pozycję zdarzenia
var clientX = event.clientX;
var offset = clientX - event.currentTarget.offsetLeft;
var newVolume = offset/event.currentTarget.offsetWidth;
setVolume(newVolume);
}

// inicjalizuj ładowanie
window.addEventListener('load',init,false);

</script>
<style>
* {
color: #000;
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
margin: 0;
padding: 0;
}
#radioContainer {
background-color: #ccc;
border: 15px solid rgba(0,0,0,.5);
-moz-border-radius: 25px;
-webkit-border-radius: 25px;
border-radius: 25px;
-moz-box-shadow: 0px 0px 4px #000;
-webkit-box-shadow: 0px 0px 4px #000;
box-shadow: 0px 0 35px rgba(1, 1, 1, 0.7), inset 0px 0 35px rgba(1, 1, 1, 0.7);
padding: 20px;
margin: 40px auto;
overflow: hidden;
width: 420px;
}

h1 {
font-size: 1.8em;
margin: 0 0 10px;
text-align: center;
}

h2 {
margin: 10px 0 5px;
}

label {
left: -9999px;
position: absolute;
}

#stationList {
background-color:#fff;
border: 1px solid #000;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
Dodawanie internetowego radia 199

color:#333;
cursor: pointer;
padding: 5px;
text-align: center;
width: 100%;
}

#radioPlayer {
float: left;
width: 100%;
}

#currentStation {
color:#333;
text-shadow:#999;
}

#radioControls {
text-align:left;
visibility:hidden;
margin-top:10px;
}

#radioControls * {
float: left;
}

#volumeControl {
border: 1px solid #fff;
cursor: pointer;
float:left;
height: 25px;
margin: 0 5px;
position:relative;
width:60px;
}

#volume_background {
background-color:#ccc;
height:25px;
width:60px;
}

#volume_bar {
background:#fff;
color:#333;
height:25px;
position:absolute;
text-align:center;
width:0px;
}

button {
background: #fff;
border: none;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
cursor: pointer;
height: 27px;
200 Rozdział 8. Osadzanie dźwięku w HTML5

padding: 0 5px;
}
</style>
</head>
<body>
<div id="radioContainer">
<h1>Odtwarzacz internetowego radia w HTML5</h1>
<audio id="audioPlayer">
<p>Twoja przeglądarka nie obsługuje elementu audio.</p>
</audio>
<div id="stations">
<label for="stationList">Rozgłośnie</label>
<select id="stationList" size="5" onChange="setStation();"></select>
</div>
<div id="radioPlayer">
<h2 id="radioStatus"></h2>
<p id="currentStation"></p>
<div id="radioControls">
<div id="volumeTitle">Głośność</div>
<div id="volumeControl" onClick="volumeChangeClicked(event);">
<div id="volume_background">
<div id="volume_bar"></div>
</div>
</div>
<button id="buttonPlayPause" onClick="playPauseClicked();"></button>
</div>
</div>
</div>
</body>
</html>

Strumienie są albo w formacie OGG, albo MP3. Strumienie OGG będą działać w Firefoksie, Operze
i Chrome; strumienie MP3 — w Chrome, IE9 i Safari. Jeśli dany format nie jest obsługiwany w przeglądarce,
powinien zostać wyświetlony komunikat o błędzie.
1. Użyj HTML-a oraz CSS-a, aby utworzyć układ i kontrolki odtwarzacza.
2. Utwórz opartą o JSON listę stacji radiowych i ich adresów URL.
3. Jeśli wystąpił błąd, użyj API do wykrycia, jakiego jest on typu.
4. Dodaj każdą stację jako element option do listy stacji.
5. Gdy stacja zostanie wybrana, uruchom funkcję setStation, która zmienia src elementu audio
i natychmiast odtwarza dźwięk.

Po wczytaniu strony uruchamiana jest funkcja init. Odpowiada ona za skonfigurowanie odtwarzacza
dźwięku, tak abyś mógł się do niego odwołać i nim manipulować, a także ustawia kontrolki, takie jak Odtwórz
i regulacja głośności. Następnie wczytuje stacje za pomocą funkcji loadStations. Funkcja ta przechodzi w pętli
przez listę JSON stacji radiowych i tworzy listę wyboru dla użytkownika. Element select zawiera zdarzenie
onChange, wywołujące funkcję setStation, która pobiera wybraną stację radiową i przypisuje jej adres URL
jako src elementu audio, a także zmienia atrybut type (na MP3 albo OGG) tego elementu, tak aby prawidłowo
się on odtworzył. Jeśli przeglądarka nie może odtworzyć danego typu dźwięku (np. w Firefoksie albo Operze
nie da się odtworzyć Radia Jazz FM, którego strumień jest zapisany w formacie MP3), sygnalizowany jest
błąd z tekstem błędu zależnym od jego typu.
Podsumowanie 201

Mimo że źródło odtwarzacza zostało określone, nie zadziała on, dopóki mu tego nie nakażesz, co jest
realizowane za pomocą radioPlayer.play(). Podczas odtwarzania strumienia generowane są zdarzenia
o nazwie durationchange. Zdarzenie to uruchamia funkcję o nazwie streamPlaying, która uaktualnia na
ekranie tekst mówiący o tym, co stacja nadaje, zmienia przycisk Odtwórz na Wstrzymaj oraz wyświetla
pozostałe kontrolki.
Ostatnie kontrolki dotyczą przycisku Odtwórz/Wstrzymaj oraz regulacji głośności. Kiedy zostaje
kliknięty przycisk Odtwórz/Wstrzymaj, uruchamiana jest funkcja sprawdzająca aktualny status: jeśli dźwięk
jest wstrzymany lub odtwarzanie się zakończyło, uruchamiane jest .play(), a w przeciwnym przypadku
.pause(). W poprzednim rozdziale, dotyczącym wideo, do regulacji głośności użyłeś nowego elementu
HTML5 — range input. Tutaj tworzona jest wersja przyjazna dla wielu przeglądarek, która wykrywa punkt,
w jakim pasek głośności został kliknięty, i aktualizuje poziom głośności dźwięku za pomocą .volume.

Podsumowanie
W tym rozdziale dowiedziałeś się, jak odtworzyć plik dźwiękowy w przeglądarce bez potrzeby użycia
zewnętrznej wtyczki, zwykle Flasha. Ponieważ jest to nowa funkcja przeglądarek, czasami zachowują
się one niezgodnie z oczekiwaniami. Podobnie jak w przypadku elementu video, należy wziąć pod uwagę
problemy z kodekami, ale w przypadku starszych przeglądarek można zastosować rozwiązania zastępcze.
Poznałeś również różne elementy nowego API, dzięki którym możesz wpływać na zachowanie pliku
dźwiękowego w przeglądarce, sterując jego odtwarzaniem i wstrzymywaniem, regulacją głośności, a także
zmianą odtwarzanego pliku.
202 Rozdział 8. Osadzanie dźwięku w HTML5
9
Dokonywanie zmian
w historii przeglądarki

W tym rozdziale omówimy aktualizacje interfejsu historii w HTML5, szczególnie dwie nowe metody
w History API (pushState i replaceState), oraz zapoznamy się z kilkoma przepisami dotyczącymi
używania ich do przechodzenia między stronami w sesji. Ponadto poznasz zdarzenie popstate, nauczysz się
używać History API do przechowywania czegoś więcej niż tylko informacji o odwiedzanych stronach oraz
zaznajomisz się z zaawansowanymi zagadnieniami, takimi jak bezpieczeństwo i dodatkowe biblioteki.

Podstawy historii
Javascriptowe History API jest używane w witrynach internetowych od JavaScriptu 1.0, lecz przed
pojawieniem się HTML5 nie było znacząco aktualizowane. Wraz z pojawieniem się AJAX-a i aplikacji
działających bez użycia kolejno ładowanych stron korzystanie z obiektu historii do przechodzenia dalej,
wstecz albo do określonego wpisu w sesji stało się problematyczne. W związku z tym wiele frameworków
aplikacyjnych, w tym YUI, zawiera własne techniki zarządzania sesją przeglądarki.
Dawniej, aby dodać stronę do historii przeglądarki bez konieczności przejścia na inną stronę, trzeba
było zmienić adres URL poprzez dodanie do niego kotwicy za pomocą symbolu #. Za pomocą metod
history.pushState i history.replaceState możesz teraz, odpowiednio, dodawać wpisy do historii i ją
modyfikować, a jeśli połączysz je ze zdarzeniem popstate okna, będziesz w stanie dać użytkownikowi ulepszone
możliwości nawigowania między stronami. Dzięki możliwości dodawania do historii i modyfikowania
historii stron na witrynie History API lepiej obsługuje przycisk Wstecz w bogatych aplikacjach korzystających
z technologii AJAX. Możesz teraz zmieniać „stan widoku” aplikacji po prostu przez pozorne dodanie strony
albo kontekstu do historii.
Możesz myśleć o historii sesji przeglądarki albo o stronach, do których użytkownik przechodził na
Twojej witrynie, jak o stosie, na szczyt którego są odkładane oglądane strony. Gdy użytkownik klika lub
dotyka przycisk Wstecz przeglądarki, strony są zdejmowane ze stosu jedna po drugiej. Korzystając jedynie
z JavaScriptu, możesz przejść dalej, wstecz albo o określoną liczbę stron w obu kierunkach za pomocą
poniższych poleceń:
204 Rozdział 9. Dokonywanie zmian w historii przeglądarki

„ window.history.forward,
„ window.history.back,
„ window.history.go([delta]).

Metody te są nadal dostępne w History API, ale teraz, w HTML5, za pośrednictwem tego API możesz
dodatkowo dynamicznie dodawać wpisy do historii, przechwytywać zdarzenia przejścia między stronami,
a nawet kontrolować kontekst stron.

Kompatybilność przeglądarek
Rozszerzenia History API są obecnie obsługiwane w przeglądarkach pokazanych w tabeli 9.1.

Tabela 9.1. Obsługa History API w przeglądarkach

Android 2.2+
Chrome 5.0+
Firefox 4.0+
Internet Explorer -
iOS Safari 3.0+
Opera 11.0+
Safari 5.0+

PRZEPIS DLA POCZĄTKUJĄCYCH


Dodawanie do historii wpisów za pomocą pushState
Użyjemy metody pushState dostępnej w History API do dodania przez bieżącą stronę w przeglądarce nowego
wpisu do stosu sesji. Metoda pobiera dwa parametry, data i title, z opcjonalnym trzecim parametrem url:
history.pushState(data, title [,url])

Parametry metody pushState są następujące:


„ data — ciąg znaków lub obiekt przekazany jako reprezentacja stanu strony,
„ title — tekst dotyczący strony wyświetlany w nagłówku przeglądarki,
„ url — (opcjonalny) nowy adres URL do dodania do stosu historii.

Parametr data reprezentuje stan strony i zostaje automatycznie powiązany z nowym wpisem. Jak później
zobaczysz, wpis ten może być pobrany przez zdarzenie okna popstate. Przez stan rozumiemy bieżący kontekst
wyświetlania strony. Może to być na przykład przepis, który dana osoba aktualnie przegląda, pochodzący
z wywołania bazy danych wykonanego choćby przez AJAX.
Niektóre przeglądarki nakładają ograniczenia na wielkość parametru data, gdyż obiekt stanu jest
przechowywany przez przeglądarkę lokalnie na dysku użytkownika. W Firefoksie ten limit wynosi 640 000
znaków serializowanej reprezentacji obiektu stanu. Jeśli masz zamiar przekroczyć ten limit lub mógłbyś
potencjalnie to zrobić, powinieneś użyć sessionStorage albo localStorage (patrz rozdział 11.).
Dodawanie do historii wpisów za pomocą pushState 205

Uwaga
Niektóre przeglądarki, na przykład Firefox, ignorują parametr title i nie wyświetlają użytkownikowi
tej wartości na liście historii sesji. Zachowanie to zależy od przeglądarki i mamy nadzieję, że zostanie
poprawione w przyszłych aktualizacjach.

Jeśli podany jest parametr url, to zastąpi on adres URL w pasku adresu przeglądarki, ale nie spowoduje,
że przeglądarka ściągnie stronę z witryny. Pozwala to użytkownikom dodać zakładkę z adresem URL i powrócić
do niego później. Oczywiście po stronie serwera potrzebny jest kod do obsługi adresów z zakładek, które nie
reprezentują prawdziwych stron. Przekazany URL może być ścieżką względną albo bezwzględną, jednak musi
się ona znajdować w tej samej domenie co aktualny adres URL. Jeśli użyta jest ścieżka względna, będzie ona
oparta na bieżącym adresie dokumentu. Domyślną ścieżką w przypadku nieprzekazania parametru URL albo
przekazania tylko łańcucha zapytania (takiego jak "?page=5") jest aktualny adres URL dokumentu.
Wykorzystanie metody pushState przypomina używanie znaku kratki (hash, #) do kontrolowania
kontekstu strony, powszechnego w dynamicznych aplikacjach zbudowanych w oparciu o AJAX:
window.location = "#foo";

Jednak metoda pushState zapewnia większą elastyczność niż dostęp oparty na znaku kratki:
„ Metoda pushState pozwala na pozostanie na tej samej stronie albo zmianę adresu URL. Jeśli zostanie
użyty znak kratki, przeglądarka pozostaje przy tym samym adresie URL.
„ Metoda pushState pozwala zachować informacje kontekstowe w stanie historii. Jak zobaczysz
w dalszej części rozdziału, może to być pomocne.

Warto podkreślić ostatni punkt, ponieważ oznacza on znaczny postęp w porównaniu ze sposobem
związanym z dodawaniem znaku kratki do adresów URL. Przy każdym wpisie w historii pushState pozwala
zapisać obiekt danych, który może przechowywać informacje kontekstowe o stanie strony. Dane mogą być
tak proste jak ciąg znaków lub tak złożone jak zserializowany obiekt JSON. Załóżmy na przykład, że masz
stronę, która pokazuje slajdy z prezentacji. Każdy slajd może mieć powiązane ze sobą informacje o tym,
jak ma być wyświetlony, na przykład dotyczące napisów, ramki, podziękowań i tak dalej. Informacje te
przechowuje obiekt stanu, dzięki czemu możesz łatwo zmienić styl strony w oparciu o slajd, przy którym
się znajdujesz. W dalszej części tego rozdziału znajdziesz przepis dotyczący wykorzystania obiektu stanu.
Użyjmy metody pushState obiektu history do dodania nowego wpisu do historii sesji. Po załadowaniu
strona sprawdzi dostępność History API w przeglądarce przez wywołanie typeof na metodzie
history.pushState. Jeżeli metoda zwraca „undefined”, to znaczy, że przeglądarka nie obsługuje History API.
Po zastosowaniu tego testu możesz użyć innego sposobu implementacji historii albo ograniczyć użytkownikowi
funkcjonalność w oparciu o jego aktualną przeglądarkę:
1. Utwórz pustą stronę HTML z elementem div, aby pokazać aktualny widok (exhibit).
2. Dodaj przycisk, który po kliknięciu uruchamia javascriptową funkcję nextExhibit.
3. Dodaj funkcję nextExhibit, by wykonać metodę pushState.
4. Dodaj wywołanie metody pushState, aby dopisać kontekst widoku surykatki do historii:
history.pushState('Surykatka','Widok surykatki','meerkats.html');
5. Zaktualizuj div widoku, aby pokazać użytkownikowi, że jest przy widoku surykatki:
document.getElementById("exhibit").innerHTML = "Przy surykatkach.";
206 Rozdział 9. Dokonywanie zmian w historii przeglądarki

Listing 9.1 zawiera cały kod strony.

Listing 9.1. Wykorzystanie pushState do dodawania stron do historii

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>9.1. W zoo</title>
</head>
<body>
<script>
// inicjalizuj obsługę przycisku
function init() {
// powiąż funkcję obsługi kliknięcia z przyciskiem
var btnNextExhibit = document.getElementById('btnNextExhibit');
btnNextExhibit.addEventListener('click',nextExhibit,false);
}

function nextExhibit() {
// Sprawdź, czy metoda pushState w History API jest dostępna

if (typeof history.pushState !== "undefined") {


// Wykonaj metodę pushState

history.pushState('Surykatka','Widok surykatki','meerkats.html');

document.getElementById("exhibit").innerHTML = "Przy surykatkach.";

} else {

// History API nie jest dostępne


alert('History API nie jest dostępne w tej przeglądarce.');

}
}

// Dodaj procedurę nasłuchu


window.addEventListener('load',init,false);
</script>
Witaj w zoo.
<div id="exhibit">Jesteś przy wejściu do zoo.</div>
<button id="btnNextExhibit">Odwiedź surykatki</button>
</body>
</html>

Gdy załadujesz stronę w przeglądarce, na podstawie tytułu strony oraz wyświetlonego na niej komunikatu
stwierdzisz, że jesteś przy wejściu do zoo. Jeśli klikniesz przycisk Odwiedź surykatki, w historii zostanie
zapisana bieżąca strona, a następnie zmieni się tytuł i adres URL okna przeglądarki bez faktycznego przejścia
do nowej strony. JavaScript zmieni komunikat informujący użytkownika o widoku surykatki. Pod każdym
względem będzie to z punktu widzenia użytkownika wyglądało tak, jakby z serwera została załadowana nowa
strona. Jest to jednak tylko nowy kontekst, który dodałeś do historii poprzez pushState.
Tworzenie przeglądarki obrazków 207

Wskazówka
W niektórych przeglądarkach, na przykład Firefox, podanie w metodzie pushState parametru url,
który ma wartość nieprawidłowej strony, spowoduje wyjątek zabezpieczeń. Aby temu zaradzić, podaj
prawidłowy adres URL lub użyj opcjonalnego parametru stanu, co usunie wyjątek.

Jeśli wyświetlisz w przeglądarce listę historii, jako ostatnią zobaczysz stronę „W zoo”, a po kliknięciu
przycisku Wstecz zostaniesz przeniesiony z powrotem do tej strony. Komunikat na stronie nie zmieni się
na „Jesteś przy wejściu do zoo.” — później dowiesz się, jak to zmienić.
Pamiętaj, że adres URL jest opcjonalny, ale może być użyteczny dla powracających na stronę
użytkowników. Oczywiście zapisane wpisy, którym na serwerze nie odpowiada prawdziwa strona,
będą musiały być obsłużone przez kod znajdujący się na serwerze.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie przeglądarki obrazków
W poprzednim przepisie, który przeprowadził Cię przez Twoje pierwsze wywołanie pushState, zapisałeś
w sesji historii jeden prosty wpis. Ten przepis idzie o krok dalej i daje użytkownikowi możliwość wyboru
różnych wariantów oraz przechowywania każdego z nich w powiększającej się historii sesji.
Wiele dostępnych witryn pozwala użytkownikom przeglądać zdjęcia, filmy lub inne treści poprzez
udostępnienie łączy będących miniaturami zdjęć i dynamiczne zastępowanie wybranego elementu. W tym
przepisie pokażesz użytkownikowi szereg miniatur obrazków, umożliwisz mu wybranie obrazka do obejrzenia
oraz dodasz nowy wpis do historii, aby śledzić, co było oglądane, i umożliwić użytkownikowi powrót do
wcześniej oglądanych obrazków. Listing 9.2 pokazuje pełny kod strony tworzący przeglądarkę obrazków
przy użyciu wpisów historii. Umieść obrazki w folderze images, aby kod funkcji showImage mógł się do nich
prawidłowo odwoływać.

Listing 9.2. Przeglądarka obrazków — tworzenie wielu wpisów w historii

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Obrazek 1</title>
<style>
div.imgView { height: 300px; }
div.imgView img { height: 300px; }
div.imgRow img { height: 100px; }
a { cursor: pointer }
</style>
</head>
<body>
<script>
// zmienna przechowująca numer bieżącego obrazka
var currentImg = 1;

// przejdź do następnego slajdu


function showImage(imgNum) {

// sprawdź, czy History API jest dostępne


if ( typeof history.pushState !== "undefined" ) {
208 Rozdział 9. Dokonywanie zmian w historii przeglądarki

// sprawdź, czy wybrany obrazek nie jest bieżącym obrazkiem


if (currentImg != imgNum) {

// ustaw tytuł obrazka


var imgTitle = 'Obrazek ' + imgNum;

// ustaw następny slajd we wpisach historii wraz z obiektem stanu i domyślnymi ustawieniami
history.pushState( imgNum, imgTitle, '?img=' + imgNum );
document.getElementById('imgSlide').src = 'images/slide' + imgNum + '.jpg';
document.getElementById('imageInfo').innerHTML = imgTitle;

// ustaw aktualny tytuł strony


document.title = 'Obrazek ' + imgNum;
var stateInfo = document.getElementById('stateInfo')
stateInfo.innerHTML = imgTitle + "<br>" + stateInfo.innerHTML;

// ustaw wybrany obrazek jako obrazek bieżący


currentImg = imgNum;
}
} else {
// History API nie jest dostępne
alert('History API nie jest dostępne w tej przeglądarce.');
}
}
</script>

<!-- widok obrazka i tytuł — ustaw na pierwszym obrazku -->


<div id='imgView' class='imgView'><img id='imgSlide' src="images/slide1.jpg" style='height:300px'></div>
<div id='imageInfo'>Obrazek 1</div>

<!-- wiersz miniaturek obrazków -->


<div id='imgRow' class='imgRow'></div>
<script>
// utwórz wiersz łączy do obrazków
var newImg;
var imgRow = document.getElementById('imgRow');
for (var i=1; i<=5; i++ ) {
document.getElementById('imgRow').add
newImg = '<a onclick="showImage('+i+');"><img class="thumbnail" src="images/slide'+i+'.jpg"></a>';
imgRow.innerHTML += newImg;

}
</script>

<!-- obszar wyświetlania stanu historii — ustaw na pierwszym obrazku (po załadowaniu strony)-->
<div id='stateInfo'>Obrazek 1</div>
</body>
</html>

Jeśli uruchomisz powyższy przepis i wybierzesz miniatury do obejrzenia, zobaczysz wpisy umieszczane
w historii przeglądarki, jakby ładowała ona oddzielne strony. Rysunek 9.1 pokazuje sytuację po wybraniu
zdjęć 3, 5, 4 i 2. Po lewej stronie rysunek pokazuje rozwijane menu przycisku Wstecz przeglądarki. Po prawej
widać to, co jest wyświetlane na stronie po wybraniu zdjęć przez użytkownika. Zauważ, że Obrazek 1 znajduje
się na dole listy, ponieważ jest to oryginalny tytuł strony HTML. Po pierwszym obrazku dynamicznie
utworzony tytuł zostaje dodany do listy historii. Ostatni wybrany i aktualnie pokazywany obrazek (Obrazek 2)
nie jest wymieniony w historii, ponieważ znajduje się on w bieżącym kontekście.
Pobieranie stanu w przeglądarce obrazków 209

Rysunek 9.1. Lista rozwijana historii przycisku Wstecz dotycząca wybranych obrazków

Uwaga
Na powyższym listingu użyliśmy włączonego do strony skryptu służącego do utworzenia przycisków
miniatur po załadowaniu strony. Normalnie posłużylibyśmy się zdarzeniem domReady poprzez framework,
taki jak jQuery. Aby zaoszczędzić miejsce i mieć kod niezależny od dodatkowych bibliotek, użyliśmy
wspomnianego skryptu.

Po uruchomieniu tego przepisu zobaczysz, że jeśli klikniesz przycisk Wstecz w przeglądarce, lista stron
w elemencie div o identyfikatorze pageInfo pozostanie taka sama. Czy nie byłoby dobrze, gdybyś mógł
przechwycić zdarzenie kliknięcia przez użytkownika przycisku Wstecz, aby zaktualizować wyświetlaną stronę?
HTML5 udostępnia w tym celu zdarzenie pobrania stanu ze stosu, co zostało omówione w kolejnym przepisie.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Pobieranie stanu w przeglądarce obrazków
W HTML5 zostało zdefiniowane nowe zdarzenie okna o nazwie popstate, które współpracuje z nowymi
metodami History API. Zdarzenie to jest wywoływane, gdy zmienia się bieżący wpis historii sesji okna
przeglądarki. Wpis w historii może się zmienić po kliknięciu przez użytkownika przycisku Wstecz lub Dalej
przeglądarki albo wywołaniu javascriptowych metod obiektu historii, takich jak back i go. W każdym z tych
przypadków możesz wykonać jakieś działania w oparciu o procedurę obsługi, służące do przechwycenia tego
zdarzenia. Składnia procedury obsługi zdarzenia ma w JavaScripcie następujący format:
window.addEventListener('popstate',funcRef,false);

Zdarzenie pobrania stanu (popstate) jest wyzwalane na javascriptowym obiekcie window i jest powiązane
z funkcją obsługi, do której odwołujemy się w powyższym wierszu jako funcRef. Jeśli zatem użytkownik
przechodzi przez swoje wpisy w historii za pomocą przycisku Wstecz, możesz wykonać wszystkie niezbędne
działania, aby na stronie HTML załadować poprawny kontekst.
Jak pamiętasz z wywołania metody pushState, wraz z każdym umieszczonym w historii wpisem możesz
zapisać dane albo stan związany z tym wpisem. Zdarzenie popstate przekazuje stan, który zapisałeś,
do procedury obsługi zdarzenia i pozwala skryptowi na dostęp do obiektu stanu. Do danych stanu możesz
się odwołać przez atrybut (tylko do odczytu) zdarzenia event.state.
Na listingu 9.2 utworzyłeś prostą przeglądarkę obrazków, która pozwala użytkownikowi wybrać
miniaturę obrazka. Po jej wybraniu obrazek jest ładowany do obszaru wyświetlania, a w historii jest
umieszczany nowy wpis dotyczący strony. Zapisanie strony w historii nie jest jednak pomocne, jeśli nie
możesz pokazać właściwego obrazka, gdy użytkownik użyje przycisku Wstecz w przeglądarce albo skrypt
210 Rozdział 9. Dokonywanie zmian w historii przeglądarki

wywoła history.back lub history.go. W tym przepisie dodasz procedurę obsługi zdarzenia pobrania stanu
w celu przechwycenia zdarzenia przejścia do innej strony i załadowania właściwego obrazka oraz jego
szczegółów. Dodaj zaznaczony fragment kodu z listingu 9.3 do listingu 9.2, wstawiając go pomiędzy istniejące
wiersze skryptu, tak jak zostało to pokazane poniżej.

Listing 9.3. Przeglądarka obrazków z pobieraniem stanu z historii


<script>

// konfiguruj obsługę zdarzenia popstate na stronie


window.addEventListener('popstate',popPage,false);

// zmienna przechowująca numer bieżącego obrazka


var currentImg = 1;

// funkcja obsługi zdarzenia pobrania stanu z historii


function popPage(event) {

// pobierz stan z historii


currentImg = event.state;

// ustaw obrazek i tytuł


var imgTitle = 'Obrazek ' + currentImg;
document.getElementById('imgSlide').src = 'images/slide' + currentImg + '.jpg';
document.getElementById('imageInfo').innerHTML = imgTitle;
document.title = imgTitle;

// pokaż, że pobraliśmy zdarzenie z historii, oraz pobrany stan


var stateInfo = document.getElementById('stateInfo')
stateInfo.innerHTML = 'Pobrano z historii: ' + imgTitle + '; stan: ' + JSON.stringify(event.state) +
´"<br>" + stateInfo.innerHTML;

// przejdź do następnego slajdu


function showImage(imgNum) {

Po włączeniu funkcji obsługi zdarzenia na listingu 9.3 uruchom kod. Kiedy strona zostanie załadowana,
wybierz drugą, a następnie trzecią miniaturę. Spowoduje to wykonanie dla każdej z nich metody pushState,
co doda je do listy pozycji w historii. Po dodaniu powyższej procedury obsługi zdarzenia popstate za każdym
razem, kiedy użytkownik kliknie w przeglądarce przycisk Wstecz lub Dalej, strona przechwyci zdarzenie
i załaduje użytkownikowi odpowiedni obrazek na podstawie zapisanego w zdarzeniu stanu.
Teraz kliknij przycisk Wstecz w przeglądarce. Zostanie wywołana procedura obsługi zdarzenia popstate.
Najpierw, przez przypisanie stanu zdarzenia do zmiennej przechowującej numer bieżącego obrazka, stan
zostanie pobrany, a następnie użyty do wyświetlenia odpowiedniego obrazka i ustawienia tytułu. W celu
weryfikacji możesz następnie zaktualizować element div o identyfikatorze stateInfo, aby zobaczyć, co zostało
pobrane z historii (patrz rysunek 9.2).
Zauważ, że zdarzenie jest wywoływane nawet po wybraniu jednej z pozycji na liście rozwijanej historii
w przeglądarce. Pobaw się z kodem, aby zobaczyć, jak zdarzenia pushState i popstate obsługują przechodzenie
po stronach historii.
Zmiana historii za pomocą replaceState 211

Rysunek 9 2. Dodawanie do historii stanu i pobieranie go

Wskazówka
Użytkownik może przechodzić wstecz w historii przeglądarki o dowolną liczbę kroków poprzez menu
historii lub polecenia History API w skryptach, takie jak go i back. Najlepiej jest powiązać element
danych z wpisu historii z kluczem, który pozwala Ci dokładnie określić, jaki jest kontekst wpisu.
Może to być na przykład unikalny numer jakiegoś elementu, numer slajdu lub jakiś inny unikatowy
indeks. W ten sposób podczas obsługiwania zdarzenia popstate kod będzie „wiedział”, który element
przetwarzać zamiast ostatniego.

PRZEPIS DLA POCZĄTKUJĄCYCH


Zmiana historii za pomocą replaceState
Metoda replaceState dostępna w History API jest stosowana w celu zastąpienia bieżącego wpisu w historii
przeglądarki nowym wpisem. Parametry są takie same jak w przypadku pushState: dane, tytuł oraz opcjonalne
pole adresu URL. Metoda replaceState przydaje się do aktualizacji stanu wpisu w historii, gdy zmienia się
kontekst, albo do ustawienia początkowego stanu strony. Metoda ma następującą postać:
history.replaceState(data, title [,url])

Po załadowaniu strony, kiedy dopiero co znalazła się ona w przeglądarce, tytuł i adres URL są zachowywane
we wpisie w historii. Jednak wraz z tą informacją nie są zapisywane żadne dane kontekstowe. Aby zapisać
dane w celu ich przechowania we wpisie w historii wraz z tytułem i adresem URL, po wczytaniu strony możesz
wywołać replaceState z tym samym tytułem, adresem URL i Twoimi dodatkowymi danymi stanu. Spowoduje
to zastąpienie aktualnego wpisu tą samą informacją o stronie oraz Twoimi dodatkowymi danymi. Po załadowaniu
strony użytkownik mógłby na przykład sprecyzować kryteria wyszukiwania, a po każdej poprawce zapisane
ustawienia byłyby zastępowane metodą replaceState.
W tym przepisie zastosowaliśmy replaceState, aby pokazać sytuację, w której strona nie jest ładowana
ponownie, ale zmienia stan bieżącego wpisu w historii. Po załadowaniu strony użytkownik może kliknąć
przycisk zastępujący obecny stan, którym jest klucz page oraz wartość idxCounter ze zaktualizowaną
wartością licznika. Wartość licznika jest po prostu zwiększana za każdym razem, kiedy stan jest zastępowany.
Aby zbudować stronę zgodnie z listingiem 9.4, wykonaj następujące kroki:
1. Utwórz pustą stronę HTML z pustym elementem div o identyfikatorze stateInfo, który będzie
wykorzystywany, aby za każdym razem zobaczyć, co ustawiasz.
2. Dodaj przycisk, który po kliknięciu uruchamia javascriptową funkcję o nazwie nextState.
3. Dodaj funkcję nextState z wywołaniem history.replaceState:
history.replaceState({page: idxCounter}, "page "+idxCounter, "?page="+idxCounter);
4. Uaktualnij w nextState element div o identyfikatorze stateInfo, pokazujący Ci, że replaceState
zostało wykonane, oraz zwiększ wartość licznika idxCounter.
212 Rozdział 9. Dokonywanie zmian w historii przeglądarki

Listing 9.4. Zastępowanie bieżącego stanu

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Strona</title>
<script>
var idxCounter = 1; // licznik przechowujący stan strony

// inicjalizuj obsługę przycisku


function init() {
// dołącz obsługę kliknięcia przycisku
var btnNextState = document.getElementById('btnNextState');
btnNextState.addEventListener('click',nextState,false);
}

// funkcja opakowująca replaceState


function nextState() {

// zastąp bieżącą stronę następną stroną


history.replaceState({page: idxCounter}, 'page '+idxCounter, '?page=' +
idxCounter);

// aktualizuj div ze stanem strony


var strStateInfo = document.getElementById('stateInfo').innerHTML;
document.getElementById('stateInfo').innerHTML = strStateInfo +
'<br>Zastąpiony stan ' + idxCounter;

// zwiększ wartość licznika


idxCounter++;

// dodaj procedurę nasłuchu do inicjalizowania strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<button id="btnNextState">Zastąp stan</button>
<div id="stateInfo"></div>
</body>
</html>

Za każdym razem, kiedy klikniesz przycisk Zastąp stan, zauważysz, że stan rzeczywiście jest zastępowany,
ponieważ w adresie URL będzie widoczny dodany przez Ciebie łańcuch zapytania: page=<idxCounter>. Jeśli
spojrzysz na rozwijane menu przycisku Wstecz przeglądarki, zauważysz również, że po wywołaniu metody
replaceState do historii nie jest dodawany nowy wpis. Zauważ, że dla stanu w replaceState zastosowałeś
inny styl danych. Użyłeś serializowanego obiektu w stylu JSON z kluczem, stroną i wartością idxCounter.
W przypadku elementu danych metod pushState lub replaceState nie jesteś ograniczony jedynie do napisów,
ale możesz zapisywać złożone obiekty.
Zmiana historii strony 213

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Zmiana historii strony
Kolejny przepis po załadowaniu strony przeprowadza w historii przeglądarki kilka czynności, używając
pushState, replaceState oraz metod back i forward. Zdarzenie popstate zostało użyte do pokazywania,
kiedy zdarzenia są wywoływane za pomocą metod historii. Listing 9.5 przedstawia kod tego przepisu.

Listing 9.5. Zapisywanie w historii i pobieranie z niej informacji

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Strona</title>
<script>

// funkcja obsługi zdarzenia popstate


function popPage(event) {
var strState = 'Pobranie — pozycja: ' + document.location + ', stan: ' + JSON.stringify(event.state);
document.getElementById('stateInfo').innerHTML += strState + '<br>';
};

function loadPages() {

logAction('zapisanie strony 1');


history.pushState({page: 1}, 'strona 1', '?page=1');

logAction('zapisanie strony 2');


history.pushState({page: 2}, 'strona 2', '?page=2');

logAction('zastąpienie strony 2 stroną 3');


history.replaceState({page: 3}, 'strona 3', '?page=3');

logAction('cofnięcie się o jeden krok');


history.back();

logAction('ponowne cofnięcie się o jeden krok');


history.back();

logAction('zrobienie dwóch kroków naprzód');


history.go(2);

function logAction(strAction) {
document.getElementById('stateInfo').innerHTML += strAction + '<br>';
alert(strAction);
}

// dodaj procedury nasłuchu zdarzeń okna


window.addEventListener('popstate',popPage,false);
window.addEventListener('load',loadPages,false);

</script>
</head>
<body>
214 Rozdział 9. Dokonywanie zmian w historii przeglądarki

<div id="stateInfo"></div>
</body>
</html>

Po załadowaniu skrypt wywołuje funkcję loadPages, która odkłada na stos dwie kolejne strony, wykonuje
replaceState, uruchamia dwa polecenia back historii, a następnie przesuwa się w niej o dwa kroki do przodu.
Oto rezultat wykonania przepisu z okna przeglądarki Firefox:
zapisanie strony 1
zapisanie strony 2
zastąpienie strony 2 stroną 3
cofnięcie się o jeden krok
Pobranie — pozycja: http://localhost/listing.9.5.html?page=1, stan: {"page":1}
ponowne cofnięcie się o jeden krok
Pobranie — pozycja: http://localhost/listing.9.5.html, stan: null
zrobienie dwóch kroków naprzód
Pobranie — pozycja: http://localhost/listing.9.5.html?page=3, stan: {"page":3}

Ponieważ po odłożeniu stron 1 i 2 następuje replaceState, w historii przeglądarki pozostają strony 1 i 3,


przy czym strona 3 jest stanem bieżącym. Teraz, kiedy masz jakieś wpisy na stosie historii, cofasz się w historii,
wywołując history.back. Wywołanie to uruchamia zdarzenie popstate i ustawia Cię z powrotem na stronie 1.
history.back zostaje ponownie wywołana, co przenosi Cię z powrotem do oryginalnej strony bez stanu. Zauważ,
że stan ma wartość null, ponieważ strona została załadowana przez przeglądarkę, a nie za pośrednictwem
pushState albo replaceState.
Wreszcie, wykonujesz history.go i idziesz naprzód o dwie strony na stosie przeglądarki. Powoduje
to powrót do strony 3 poprzez przeskok z oryginalnej strony do strony 1, a następnie do strony 3 (jako
że strona 2 została wcześniej zastąpiona stroną 3). Może to być mylące i może łatwiej Ci będzie rysować to
na kartce papieru, w miarę jak pokazywane są komunikaty. Najprostszym sposobem, jaki udało nam się
znaleźć, jest stworzenie rysunku wieży z bloków, z których każdy reprezentuje wpis w historii. Wymiana
wpisu zastępuje blok, podczas gdy poruszanie się po historii przesuwa jedynie aktualny wskaźnik do jakiegoś
bloku na stosie (patrz rysunek 9.3).

Rysunek 9.3. Wpisy stosu historii i bieżący wskaźnik podczas wykonywania przepisu

Należy pamiętać, że zdarzenie popstate nie zadziała, dopóki nie zostanie wywołane zdarzenie okna
onload. Jeśli przed zdarzeniem okna onload będą wywoływane metody, które zwykle aktywowałyby zdarzenie
popstate, wtedy zazwyczaj tylko ostatnie zdarzenie popstate zostanie aktywowane w przeglądarce.

Wskazówka
Ten przepis pokazuje, jak metoda replaceState może zastąpić stan strony po wykonaniu metody
pushState. Jak widziałeś, stan oryginalnej strony to null, ponieważ oryginalna strona została
załadowana przez przeglądarkę, a nie przez pushState. Jeśli musisz powiązać stan z oryginalną
stroną, po załadowaniu strony wykonaj replaceState w celu powiązania strony z Twoimi danymi stanu.
Używanie zaawansowanych obiektów danych stanu do przenoszenia informacji 215

PRZEPIS DLA ZAAWANSOWANYCH


Używanie zaawansowanych obiektów danych stanu
do przenoszenia informacji pomiędzy stronami
Zgodnie ze składnią metod pushState i replaceState pierwszym parametrem są dane, które reprezentują
stan strony, i przyjmuje on albo ciąg tekstowy, albo obiekt. Dotychczas jako parametr danych przekazywałeś
jedynie ciągi tekstowe albo małe klucze i obiekty wartości. W wielu przypadkach mamy znacznie więcej
informacji o kontekście strony, które dobrze byłoby przechowywać wraz z wpisem historii, dzięki czemu
kiedy użytkownik powróci do strony, można mu pokazać jej zawartość bez odwoływania się do informacji
znajdujących się poza przeglądarką.
Obiekty mogą być przekazywane do metod z wykorzystaniem reprezentacji JSON, jak pokazano
w poniższym przykładzie z jedną zmienną w tym formacie:
var stateObj = { page: 1 };
history.pushState(stateObj, 'Tytuł 1', 'page1.html');

Poniższy kod pokazuje, jak mogą być przekazywane bardziej złożone obiekty:
var stateObj = { page: 1, title: "Mój slajd numer 1", author: "Paweł"};
history.pushState(stateObj, 'Slajd 1', 'slide1.html');

Obiekt danych stanu, wprowadzany do historii, jest doskonałym sposobem na przechowywanie wyborów
użytkownika, preferencji albo działań wykonanych na stronie. Zauważ jednak, że obiekty danych stanu gubią
się, jeśli użytkownik wyczyści historię przeglądarki, i mogą mieć ograniczenia ilości danych określone przez
poszczególne przeglądarki. Jednak w przypadku większości zastosowań nie powinno to stanowić problemu.
W tym przepisie nauczysz się przechowywać dane we wpisach w historii w formacie JSON, a następnie
wyciągać je z powrotem przez zdarzenie window.onpopstate. Listing 9.6 wyświetla prosty pokaz slajdów przy
użyciu obrazków slajdów i preferencji użytkownika. Preferencje użytkownika są przechowywane w każdym
wpisie w historii, tak aby po przejściu za pomocą przycisku Wstecz do wcześniejszego slajdu preferencje
zostały przywrócone. Przywrócenie preferencji odbywa się w procedurze obsługi zdarzenia popstate.

Listing 9.6. Prezentacja slajdów — odkładanie stron z danymi

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Slajd 1</title>
</head>
<body>
<script>
// ustaw numery pierwszego i ostatniego slajdu
var minSlide = 1;
var maxSlide = 5;

// inicjalizuj użyte pola


var currentSlide = 1;
var currentTitle = "Mój slajd numer 1";
var borderOn = 0; // 0 oznacza wyłączone, 1 oznacza włączone
var slideNote = "";
216 Rozdział 9. Dokonywanie zmian w historii przeglądarki

// inicjalizuj stan pierwszego slajdu przez zastąpienie bieżącego stanu


var stateObj = { slide: currentSlide, border: borderOn, note: slideNote };
history.replaceState(stateObj, currentTitle, '?slide=' + currentSlide);

// obsługa pobrania stanu z historii


window.onpopstate = function(event) {

// pokaż adres URL i napis do wyświetlenia związany z event.state


document.getElementById('stateInfo').innerHTML = "adres: " + document.location + "<br>stan: " +
´JSON.stringify(event.state);

// pobierz dane obiektu stanu


currentSlide = event.state.slide;
borderOn = event.state.border;
slideNote = event.state.note;

// pokaż bieżący slajd


showSlide();
}

// przejdź do następnego slajdu


function nextSlide() {

// sprawdź, czy History API jest dostępne


if ( typeof history.pushState !== "undefined" ) {

// potwierdź, że nie jesteśmy na końcu prezentacji


if ( currentSlide < maxSlide ) {

// pobierz wszystkie notatki, które zostały wprowadzone


slideNote = document.getElementById('txtNote').value;

// ustaw bieżące opcje w obiekcie


var currentStateObj = { slide: currentSlide, border: borderOn, note: slideNote };

// zastąp właściwości bieżącego slajdu w bieżącym wpisie w historii


history.replaceState( currentStateObj, 'Slajd ' + currentSlide + ' ' + slideNote, "?slide=" +
´currentSlide);

// zwiększ indeks bieżącego slajdu


currentSlide++;

// ustaw zmienne globalne dla następnego slajdu, przywracając je do wartości domyślnych


borderOn = 0;
slideNote = "";
document.getElementById('stateInfo').innerHTML = "";

// ustaw następny slajd we wpisach w historii z obiektem stanu i wartościami domyślnymi


var nextStateObj = { slide: currentSlide, border: borderOn, note: slideNote };
history.pushState( nextStateObj, 'Slajd ' + currentSlide, "?slide=" + currentSlide );

// pokaż bieżący slajd


showSlide();
}
} else {
// History API nie jest dostępne
alert('History API nie jest dostępne w tej przeglądarce.');
}
}
Używanie zaawansowanych obiektów danych stanu do przenoszenia informacji 217

// przejdź do poprzedniego slajdu


function prevSlide() {

// potwierdź, że nie jesteśmy już na początku


if (currentSlide>minSlide) {

// cofnij się o jeden krok w historii


history.back();
}
}

// pokaż bieżący slajd, tytuł i opcje


function showSlide() {

// ustaw bieżący slajd i tytuł


document.getElementById('imgSlide').src = "images/slide" + currentSlide + ".jpg";
document.getElementById('slideInfo').innerHTML = "Slajd " + currentSlide;

// ustaw tytuł bieżącej strony


document.title = "Slajd " + currentSlide;

// ustaw opcje bieżącego slajdu


if (borderOn == 1) {
document.getElementById('imgSlide').style.border = "5px solid #000000";
document.getElementById('chkBorder').checked = 1;
} else {
document.getElementById('imgSlide').style.border = "";
document.getElementById('chkBorder').checked = 0;
}
document.getElementById('txtNote').value = slideNote;
}

// obsłuż zmianę opcji obramowania obrazka


function setImgBorder() {

// ustaw obramowanie w oparciu o przycisk wyboru i właściwość globalną


if (document.getElementById('chkBorder').checked == 1) {
document.getElementById('imgSlide').style.border = "5px solid #000000";
borderOn = 1;
} else {
document.getElementById('imgSlide').style.border = "";
borderOn = 0;
}
}
</script>

<!-- obrazek slajdu i tytuł -->


<div id='slide' style='height:100px;'><img id='imgSlide' src="images/slide1.jpg"></div>
<div id='slideInfo'>Slajd 1</div>

<!-- opcje slajdu -->


<input type="checkbox" id="chkBorder" onChange="setImgBorder();">Obramowanie<br>Notatka: <input type="text"
´id="txtNote" value=""><br>

<!-- przyciski przechodzenia między slajdami -->


<input type="button" onclick="prevSlide();" value="Poprzedni slajd" />
<input type="button" onclick="nextSlide();" value="Następny slajd" />
218 Rozdział 9. Dokonywanie zmian w historii przeglądarki

<!-- obszar wyświetlania stanu historii -->


<div id='stateInfo'></div>
</body>
</html>

Ten kod pozwala użytkownikowi dodać ramkę obrazka oraz wprowadzić notatkę dotyczącą każdego
slajdu. Możesz udostępnić dowolną liczbę opcji. Służący do debugowania element div o identyfikatorze
stateInfo pokazuje kontekst danych, w miarę jak wpisy są pobierane z historii. Stan obiektu JSON możesz
wyświetlić za pomocą metody JSON.stringify. Do każdej przechowywanej wartości obiektu stanu możesz
się odwołać za pomocą klucza. Aby uzyskać wartość stanu ramki, należy wywołać event.state.border.
Kiedy użytkownik kliknie w celu przejścia do następnego slajdu, tworzony jest obiekt z aktualnymi
ustawieniami i numerem slajdu. Jest on następnie przekazywany do wywołania replaceState, aby zapisać
stan przed pokazaniem następnego slajdu. Następnie przywracane są domyślne ustawienia i pokazywany
jest następny w kolejności slajd. Oczywiście stan wcześniej wyświetlanych slajdów, które pojawiają się po
bieżącym slajdzie, nie jest brany pod uwagę, ponieważ każdy następny slajd jest umieszczany we wpisach
w historii wraz ze swoim początkowym stanem. Aby rozwiązać ten problem, możesz użyć pewnych nowych
w HTML5 technik przechowywania danych po stronie klienta, które poznasz w rozdziale 11.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Testowanie bezpieczeństwa historii
Za każdym razem, kiedy chcesz zmienić historię przeglądarki, tytuł strony oraz adres URL, musisz myśleć
o bezpieczeństwie. Zmiana adresów URL była w przeszłości jedną z bardziej popularnych metod phishingu
(fałszowania witryn). Nowe History API po raz pierwszy udostępnia programistom metodę zmiany
zawartości adresu URL bez rzeczywistego ładowania strony. Jednak w specyfikacji HTML5 dla różnych
przeglądarek określone są zabezpieczenia, których przestrzeganie chroni przed nadużywaniem tego API:
„ Skrypt nie może ustawić w adresie URL metod pushState i replaceState domeny innej niż bieżąca.
„ W celu zachowania poufności danych wymienianych pomiędzy witrynami zdarzenie popstate może
się odwoływać tylko do obiektów stanu zapisanych w historii przez strony z taką samą domeną.
„ Istnieje ograniczenie liczby wpisów, które strona może dodać do stosu historii przeglądarki metodą
pushState, aby zapobiec przed „zalewaniem” historii przeglądarki użytkownika.

Dzięki takim regułom zachowań przeglądarki możliwość niewłaściwego wykorzystania History API
jest zminimalizowana. Można sprawdzić działanie jednej z tych reguł, próbując zmienić adres URL na adres
w innej domenie.

Uwaga
Przeglądarki mogą nałożyć ograniczenia i przycinać stos historii, aby zapobiec przeciążeniu potencjalnymi
atakami „zalewania”. Każda przeglądarka ma swoją ustaloną liczbę wpisów, ale kolejność usuwania
jest zgodna z metodą FIFO (ang. first in, first out — pierwszy na wejściu, pierwszy na wyjściu).

Odbyło się wiele dyskusji na temat zezwalania JavaScriptowi na sterowanie wyświetlaniem adresu URL
przeglądarki bez rzeczywistego dokonywania zmiany strony lub powodowania załadowania nowej strony.
Główne obawy budzi to, że metody takie jak pushState lub replaceState mogą być użyte do wyłudzania
Testowanie bezpieczeństwa historii 219

osobistych i poufnych informacji, gdyż użytkownikowi może się wydawać, że znajduje się w innym miejscu,
niż jest w rzeczywistości. Możesz sobie wyobrazić, co by się działo, jeśli wszyscy mieliby możliwość zmieniania
adresu na całkowicie dowolny. Przeglądarki jednak, zgodnie ze specyfikacją History API, muszą walidować
adres używany w parametrze url. Jeśli używana jest ścieżka bezwzględna, adres musi się znajdować w tej
samej domenie co oryginalna strona. Sprawdźmy, czy przeglądarka chroni przed potencjalnym nadużyciem
przez próbę zapisania w historii strony z inną domeną. Listing 9.7 pokazuje bardzo prostą stronę
umożliwiającą zapisanie nowego stanu. Wypróbuj go i sprawdź, czy działa w Twojej przeglądarce,
przez wykonanie następujących kroków i utworzenie kopii listingu 9.7:
1. Utwórz pustą stronę HTML, która ma przycisk uruchamiający funkcję pushPage.
2. Dodaj funkcję pushPage, która sprawdza dostępność metody pushState, a następnie zapisuje
nowy kontekst w postaci page.html.

Listing 9.7. Proste zapisanie adresu w tej samej domenie

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>9.7. Zapisywanie stron w tej samej domenie</title>
</head>
<body>
<script>
// inicjalizacja obsługi przycisku
function init() {
// załącz obsługę kliknięcia przycisku
var btnPushPage = document.getElementById('btnPushPage');
btnPushPage.addEventListener('click',pushPage,false);
}

// odłóż nowy stan w historii


function pushPage() {

// sprawdzamy, czy History API jest dostępne


if (typeof history.pushState !== "undefined") {

// odłóż nowy stan


history.pushState(null, 'Dobra strona', 'page.html');
} else {

// History API nie jest dostępne


alert('History API nie jest dostępne w tej przeglądarce.');
}
}

// dodaj procedurę nasłuchu służącą do inicjalizacji strony


window.addEventListener('load',init,false);

</script>
<button id="btnPushPage">Spróbuj wykonać zapis w historii</button>
</body>
</html>

Zmodyfikujmy teraz nieco powyższy listing, aby zobaczyć, co się stanie, jeśli ktoś spróbuje użyć metody
pushState do sfałszowania domeny URL. Zmień parametr url metody pushState, jak pokazano na listingu
220 Rozdział 9. Dokonywanie zmian w historii przeglądarki

9.8, na ścieżkę bezwzględną domeny innej niż ta, na której aktualnie uruchamiasz stronę. Jako przykład
wybraliśmy www.asite.com.

Listing 9.8. Ustawianie innej domeny

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>9.8. Ustawianie innej domeny</title>
</head>
<body>
<script>
// inicjalizacja obsługi przycisku
function init() {
// załącz obsługę kliknięcia przycisku
var btnPushPage = document.getElementById('btnPushPage');
btnPushPage.addEventListener('click',pushPage,false);
}

// odłóż nowy stan w historii


function pushPage() {

// sprawdzamy, czy History API jest dostępne


if (typeof history.pushState !== "undefined") {

// odłóż nowy stan


history.pushState(null, 'Zła strona', 'http://www.asite.com/fish.html');
} else {

// History API nie jest dostępne


alert('History API nie jest dostępne w tej przeglądarce.');
}
}

// dodaj procedurę nasłuchu służącą do inicjalizacji strony


window.addEventListener('load',init,false);

</script>
<button id="btnPushPage">Spróbuj wykonać zapis w historii</button>
</body>
</html>

Kiedy przeglądarka zapisuje nowy stan w historii za pomocą metody pushState, sprawdza przekazany
adres URL. Jeśli adres URL jest pełną ścieżką, a domena jest różna od tej, która jest „nadpisywana”, wywołanie
nie powiedzie się, zwróci wyjątek albo po prostu nie zrobi niczego, zależnie od przeglądarki. To samo dotyczy
metody replaceState.

Pomocne biblioteki
Jako twórcy stron internetowych musimy się interesować nie tylko aspektami bezpieczeństwa History API,
ale również obsługą tego API w różnych przeglądarkach, a w ramach danej przeglądarki w różnych jej wersjach.
W przypadku kilku nowych API HTML5 poziom ich implementacji w poszczególnych przeglądarkach
znacznie się różni, a kompatybilność wstecz w przewidywalnej przyszłości wciąż będzie problemem, dopóki
Podsumowanie 221

większość użytkowników nie zaktualizuje ich do nowych wersji. W przypadku History API oznacza to,
że dla uzyskania kompatybilności wstecz funkcjonalności zapamiętywania stanu strony musisz nadal
obsługiwać metodę z kotwicą w adresie oraz zdarzenie hashChange:
window.onhashchange = function() {
alert("Kotwica została zmieniona!");
};
window.location.hash = Math.random();

Nie oznacza to jednak, że musisz zrezygnować z korzyści płynących z nowych funkcji historii HTML5.
Jak w przypadku większości problemów ze zgodnością przeglądarek, inni programiści byli świadomi istnienia
tego problemu i stworzyli biblioteki do obsługi różnic nie tylko między przeglądarkami, ale także pomiędzy
ich wersjami. W przypadku History API główną biblioteką jest w tej chwili history.js.
Oczywiście możesz samodzielnie zaprogramować obsługę różnic między przeglądarkami, ale możesz
także ściągnąć z GitHuba (https://github.com/balupton/History.js) plik history.js, udostępniający prostą
opakowującą bibliotekę javascriptową, która używa metod historii HTML5, jeśli są obsługiwane, lecz w razie
potrzeby automatycznie odwołuje się awaryjnie do metody z kotwicą. Ogólnie składnia jest podobna do
metod, zdarzeń i atrybutów History API, które widziałeś w tym rozdziale. Niestety nie mamy tutaj miejsca
na wypróbowanie tej biblioteki, ale udostępnia ona następujące funkcje:
„ obsługa wielu przeglądarek,
„ wsparcie frameworków aplikacyjnych, w tym jQuery, MooTools i Prototype,
„ wsteczna kompatybilność ze starszymi przeglądarkami za pomocą znaczników ze znakiem #.

Podsumowanie
History API dostępne w JavaScripcie ma bardzo użyteczne funkcje i udostępnia twórcom stron internetowych
możliwość zmiany historii użytkownika w danej witrynie bez zmieniania samej strony. Witryny takie jak
GitHub i Flickr już zaczęły z powodzeniem używać History API, dzięki czemu są bardziej przyjazne dla
użytkownika.
W tym rozdziale zbadałeś, jak metody pushState i replaceState oraz zdarzenie popstate działają
w połączeniu z listą wpisów historii (patrz rysunek 9.4).

Rysunek 9.4. History API z metodami pushState i replaceState oraz zdarzeniem popstate
222 Rozdział 9. Dokonywanie zmian w historii przeglądarki

Oto główne techniczne możliwości, które HTML5 dodaje teraz do historii:


„ odkładanie nowych wpisów w historii wpisów przeglądarki,
„ zastępowanie danych stanu bieżącego wpisu w historii,
„ zarządzanie obsługą zdarzeń przechodzenia między stronami i pobieraniem stanu.

Powinieneś mieć teraz wyobrażenie o tym, w jaki sposób możesz zastosować te możliwości we własnej
witrynie albo aplikacji.
10
Wykorzystanie Geolocation API
do uzyskania informacji
o położeniu geograficznym

W celu oferowania użytkownikom lepszych usług, na przykład w zakresie informacji o najbliższym


sklepie lub atrakcyjnych wydarzeniach w okolicy, witryny internetowe od lat korzystają z funkcji
lokalizacji. Dane o położeniu były niegdyś zbierane za pośrednictwem adresu IP przeglądarki, poprzez
dopasowywanie go w bazie danych, lub po prostu dzięki zapytaniu użytkownika o to, gdzie się znajduje.
W związku z dostępnością smartfonów z wbudowanymi odbiornikami GPS znacząco wzrosła liczba aplikacji,
które potrafią określić lokalizację użytkownika. HTML5 i Geolocation API pozwalają witrynom i aplikacjom
internetowym w prosty i stosunkowo niezawodny sposób uzyskać dostęp do informacji o położeniu
geograficznym urządzenia, na którym działa przeglądarka. Dzięki przepisom z tego rozdziału poznasz obiekty
i metody Geolocation API umożliwiające pobieranie informacji o położeniu geograficznym urządzenia
przeglądarki i wykorzystanie tych informacji w Twojej aplikacji.

Omówienie Geolocation API


Określenie położenia geograficznego przeglądarki — czy to na laptopie, czy na urządzeniu przenośnym
— wiąże się z uzyskaniem kluczowych informacji, które mogą być wykorzystane do różnych celów, w tym:
„ wyświetlania pozycji przeglądarki na mapie,
„ wyświetlania specyficznych dla położenia geograficznego informacji albo ciekawostek,
„ dodawania danych dotyczących położenia do wpisywanych przez użytkownika informacji,
takich jak opinie o miejscach albo fotografie.

Uzyskanie dostępu do informacji o lokalizacji użytkownika na podstawie adresu IP przeglądarki może


być problematyczne, ponieważ baza danych adresów IP i lokalizacji geograficznych musiałaby być niezwykle
rozbudowana i na bieżąco aktualizowana. Dane o lokalizacji mogą być również nieprecyzyjne i zapewniać
informacje jedynie z dokładnością do pewnego obszaru. W celu pokonania trudności związanych z lokalizacją
adresów IP witryny często proszą użytkownika o podanie kodu pocztowego lub pełnego adresu — jednak
informacja ta dotyczy wyłącznie aktualnej, a nie przyszłej lokalizacji użytkownika.
224 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Będące częścią HTML5 Geolocation API udostępnia wbudowane metody, które umożliwiają uzyskanie
dość szczegółowych informacji o lokalizacji.
Dzięki Geolocation API przeglądarka może teraz za pomocą wartości szerokości i długości geograficznej
podać Ci z określoną dokładnością swoje położenie na świecie. Stopień dokładności zależy od kilku czynników,
a programiści mogą mieć na niego wpływ. Czy zastanawiasz się, po co Ci jakakolwiek szerokość i długość
geograficzna, jeśli nie znasz współrzędnych geograficznych kawiarni za rogiem? Otóż aby odnajdywanie
położenia geograficznego było przydatne, metody opisywania lokalizacji muszą być uniwersalne i jednolite,
co zapewnia właśnie układ współrzędnych geograficznych. Jak dowiesz się z dalszej części tego rozdziału,
istnieją usługi, które na podstawie pobranych współrzędnych geograficznych dostarczają dodatkowych
informacji. Dzięki wykorzystaniu zbioru współrzędnych, szczególnie biorąc pod uwagę duże geograficzne
bazy danych takich dostawców jak Google, łatwo jest także wykonać odwrócone geokodowanie (patrz
wskazówka poniżej).

Odwrócone geokodowanie
Odwrócone geokodowanie — przeciwieństwo geokodowania — jest praktyką przekształcania pary
współrzędnych geograficznych na adres fizyczny. Takich informacji dostarczają różni usługodawcy
— często wykorzystywane są na przykład usługi Google Maps JavaScript API v3 (https://developers.
google.com/maps/documentation/javascript/services#ReverseGeocoding).

Kompatybilność przeglądarek
Geolocation API to wciąż nowa funkcjonalność, ma jednak tak duże możliwości, że odpowiednie definicje
API są możliwie szybko przyswajane przez najpopularniejsze przeglądarki. Tabela 10.1 pokazuje aktualny
stan obsługi API przez przeglądarki.

Tabela 10.1. Dostępność Geolocation API w przeglądarkach

Android 2.1+
Chrome 9.0+
Firefox 3.5+
Internet Explorer 9.0+
iOS Safari 3.2+
Opera 10.6+
Safari 5.0+

Gdzie na świecie się znajdujesz — getCurrentPosition


Podstawową funkcją Geolocation API jest odnajdywanie aktualnego położenia przeglądarki na świecie.
Metoda getCurrentPosition udostępnia Ci tę informację w asynchronicznym wywołaniu javascriptowym.
Należy zwrócić uwagę na to, że wywołania javascriptowe, które ustalają lokalizację, są z natury asynchroniczne.
Większość JavaScriptu jest wykonywana synchronicznie w głównej ścieżce programu. Za pomocą
asynchronicznych wywołań metod JavaScript wykonuje wywołania w tle, a następnie, gdy ten proces
się zakończy, zwraca wyniki do funkcji. Dzięki temu, że wywołanie API jest asynchroniczne, wyniki
zapytania mogą być wyświetlone użytkownikowi bez wpływu na przetwarzanie reszty strony.
Ustalanie położenia geograficznego za pomocą zwykłego wywołania getCurrentPosition 225

Metoda getCurrentPosition posiada jeden wymagany parametr (nazwę funkcji zwrotnej wywoływanej
w przypadku pomyślnego uzyskania informacji o lokalizacji) oraz dwa parametry opcjonalne (funkcja
zwrotna wywoływana w przypadku błędu i obiekt z opcjami):
getCurrentPosition (udaneWywołanie [, nieudaneWywołanie] [, opcjeLokalizacji])

W skład parametrów tej metody wchodzą:


„ udaneWywołanie — funkcja, która ma być wykonana i której zostaną przekazane współrzędne;
„ nieudaneWywołanie — (opcjonalny) funkcja do obsługi wszelkich błędów, które wystąpiły;
„ opcjeLokalizacji — (opcjonalny) obiekt zawierający opcje dotyczące pobierania informacji
o położeniu.

Jako że wywołanie getCurrentPosition jest asynchroniczne, metoda musi zostać poinformowana,


jakie funkcje będą wywołane po zakończeniu wykonywania metody w przypadku pomyślnego uzyskania
informacji o lokalizacji oraz w przypadku potencjalnej porażki. Przejdźmy teraz do przepisu, który pozwoli
określić Twoje położenie geograficzne.

PRZEPIS DLA POCZĄTKUJĄCYCH


Ustalanie położenia geograficznego
za pomocą zwykłego wywołania getCurrentPosition
W tym przepisie użyjemy metody getCurrentPosition z funkcją wywołania zwrotnego, aby w przypadku
pomyślnego uzyskania informacji o lokalizacji określić Twoje aktualne położenie geograficzne i wyświetlić
właściwości zwróconego obiektu zawierającego te informacje. Wykorzystaj poniższe kroki i kod z listingu
10.1, aby zrealizować przepis:
1. Utwórz pustą stronę HTML z elementem div (o identyfikatorze btnFindMe) i przyciskiem Znajdź
mnie, który wywoła funkcję findMe, kiedy zostanie kliknięty.
2. Pomiędzy znacznikami script wstaw funkcję findMe wraz z poniższym kodem weryfikującym
obsługę Geolocation API, a następnie wywołaj metodę getCurrentPosition:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(geoSuccess);
} else {
document.getElementById('myLocation').innerHTML =
"Geolocation API nie jest obsługiwane";
}
3. Dodaj funkcję geoSuccess, która będzie funkcją wywołania zwrotnego w przypadku pomyślnego
wykonania żądania getCurrentPosition.
4. Dodaj drugi element div (o identyfikatorze myLocation), w którym wyświetlisz zwrócone przez
getCurrentPosition informacje o położeniu.

Listing 10.1. Odnajdywanie lokalizacji przeglądarki za pomocą getCurrentPosition

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
226 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

<title>10.1. Znajdź mnie</title>


<script>

// inicjalizuj stronę procedurami nasłuchu innych zdarzeń


function init() {
var btnFindMe = document.getElementById('findMe');
btnFindMe.addEventListener('click',findMe,false);
}

// funkcja wywołania zwrotnego w przypadku pomyślnego wykonania getCurrentPosition


function geoSuccess(position) {

// pobierz pozycję DOMTimeStamp do wyświetlenia


var dateDisplay = new Date(position.timestamp);

// pobierz odwołanie do elementu div z wynikami


var myLocationDiv = document.getElementById('myLocation');

// wyświetl pola współrzędnych i obiektu znacznika czasu


myLocationDiv.innerHTML = 'Szerokość: ' + position.coords.latitude + '<br>' +
'Długość: ' + position.coords.longitude + '<br>' +
'Dokładność: ' + position.coords.accuracy + '<br>' +
'Wysokość (opcjonalnie): ' + position.coords.altitude + '<br>' +
'Dokładność wysokości (opcjonalnie): ' + position.coords.altitudeAccuracy + '<br>' +
'Kierunek (opcjonalnie): ' + position.coords.heading + '<br>' +
'Prędkość (opcjonalnie): ' + position.coords.speed + '<br>' +
'Pozycja DOMTimeStamp: ' + position.timestamp + '<br>' +
'Data i czas: ' + dateDisplay.toLocaleString();
}

// funkcja wywoływana po kliknięciu przycisku służącego do znalezienia lokalizacji


function findMe() {
var myLocationDiv = document.getElementById('myLocation');
// sprawdź obsługę położenia geograficznego
if (navigator.geolocation) {
// wykonaj asynchroniczne wywołanie getCurrentPosition
navigator.geolocation.getCurrentPosition(geoSuccess);
myLocationDiv.innerHTML = 'Uzyskiwanie informacji o Twoim położeniu.';
} else {
// Geolocation API nie jest obsługiwane
myLocationDiv.innerHTML = 'Geolocation API nie jest obsługiwane';
}
}

// inicjalizuj stronę po załadowaniu


window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id='btnFindMe'>
<button id="findMe">Znajdź mnie</button>
</div>
<div id="myLocation"></div>
</body>
</html>
Ustalanie położenia geograficznego za pomocą zwykłego wywołania getCurrentPosition 227

Po kliknięciu przycisku Znajdź mnie zostanie wywołana funkcja findMe(). Pierwszym zadaniem funkcji
jest sprawdzenie, czy przeglądarka, której używasz, obsługuje Geolocation API. Zadanie to jest realizowane
za pomocą następującego kodu:
if (navigator.geolocation) {

Jeśli obiekt navigator.geolocation jest dostępny, możesz wykonać metodę getCurrentPosition.


W przeciwnym przypadku poprzez wyświetlenie odpowiedniego komunikatu zostanie zasygnalizowany
brak obsługi API. Po pozytywnej weryfikacji dostępności API wywoływana jest metoda getCurrentPosition
z nazwą funkcji zwrotnej geoSuccess jako argumentem. Funkcja ta zostanie wykonana po zakończeniu
getCurrentPosition.
Po wywołaniu metody getCurrentPosition pierwszą akcją, którą wykona przeglądarka, będzie sprawdzenie,
czy użytkownik zezwolił na udostępnienie tej informacji na stronie, lub poproszenie go, aby to zrobił.
W zależności od używanej przeglądarki komunikat i opcje mogą się nieznacznie różnić. Na przykład
w Firefoksie pojawia się opadający z górnej części ekranu panel, który pozwala użytkownikowi na podjęcie
decyzji o udostępnieniu swojej lokalizacji oraz na zapamiętanie wyboru dla witryny. W Safari pojawi się okno
dialogowe, które pozwoli użytkownikowi wybrać między zezwoleniem, brakiem zezwolenia i zezwoleniem
na najbliższe 24 godziny.
Kiedy przeglądarka zostanie upoważniona do pobrania danych o Twoim położeniu geograficznym,
wykorzysta do jego określenia Wi-Fi oraz informacje z sieci komórkowej, o ile są one dostępne. Dane
zostaną przekazane jako obiekt lokalizacji do funkcji zwrotnej wywoływanej po pomyślnym wykonaniu
metody. Obiekt lokalizacji przechowuje właściwości dotyczące położenia, w tym szerokość i długość
geograficzną (patrz tabela 10.2).

Tabela 10.2. Zwracany obiekt lokalizacji

Obiekt Właściwość Typ Cel


coords latitude double wartość szerokości geograficznej
longitude double wartość długości geograficznej
accuracy double margines błędu wartości szerokości i długości
geograficznej w metrach
altitude double (opcjonalna) wartość wysokości, jeśli jest dostępna
altitudeAccuracy double (opcjonalna) dokładność wartości wysokości
heading double (opcjonalna) kierunek liczony od północy w stopniach,
jeśli jest dostępny
speed double (opcjonalna) prędkość, jeśli jest dostępna
timestamp DOMTimeStamp aktualny lokalny czas i data

Dane obiektu lokalizacji są podzielone na obiekt coords i obiekt timestamp w formacie DOMTimeStamp.
Wewnątrz funkcji geoSuccess możesz teraz wyświetlać różne właściwości obiektu coords, odwołując się
do każdej z nich przez główny obiekt lokalizacji. Aby na przykład pobrać szerokość geograficzną lokalizacji,
używasz position.coords.latitude. Każda przeglądarka obsługuje pola opcjonalne nieco inaczej. Rysunek
10.1 pokazuje wyniki z Chrome.
228 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Rysunek 10.1. Twoje położenie geograficzne pokazane w Chrome

Poufność informacji o położeniu


Znajomość położenia przeglądarki, a więc położenia użytkownika, może być uznana za informację poufną.
Jak zobaczyłeś w pierwszym przepisie, zgoda na dzielenie się przez użytkownika swoimi prywatnymi danymi
dotyczącymi położenia jest uzyskiwana dzięki autoryzacji. Dopóki użytkownik nie umożliwi albo nie zablokuje
dostępu do swoich danych, wywołanie API getCurrentPosition będzie zawieszone. To właśnie z tego względu
wywołanie to jest wykonywane za pomocą metody asynchronicznej, tak żeby pozostała część strony nie
blokowała się, czekając na zezwolenie lub odpowiedź użytkownika.
W tym miejscu możesz się zastanawiać, co się stanie, jeśli użytkownik nie udzieli zezwolenia na uzyskanie
informacji lub informacja ta ulegnie przedawnieniu. W tym miejscu na scenie pojawia się parametr obsługi
błędu metody getCurrentPosition, któremu przyjrzymy się w kolejnym przepisie.

Wskazówka
W miarę korzystania z Geolocation API możesz zauważyć, że w niektórych przypadkach Twoja strona
nie zwraca żadnego wyniku lub zwraca błąd przekroczenia limitu czasu (co zobaczysz w następnym
przepisie). Zwykle będzie to rezultat błędu jednej z funkcji zwrotnych Twojego kodu. Ponieważ metody
dotyczące lokalizacji są asynchroniczne, błędy te — zależnie od przeglądarki — mogą (ale nie muszą)
przedostawać się do okna przeglądarki. Do identyfikacji problemu może się przydać wykorzystanie
w funkcjach zwrotnych debugowania za pomocą wywołań console.log().

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Wyświetlanie na mapie informacji o lokalizacji
przy użyciu getCurrentPosition
W tym przepisie użyjemy metody getCurrentPosition, aby pobrać dane o lokalizacji przeglądarki i wyświetlić
je na stronie na mapie Google. Do kodu dołączymy procedurę obsługi błędów, przydatną, gdy metoda
getCurrentPosition zwróci błąd (co sprawdzisz, gdy tylko będziesz miał poprawnie działającą stronę).
Analogicznie do poprzedniego przepisu, kiedy strona zostanie załadowana, użytkownik może kliknąć
przycisk Pokaż mnie na mapie, który uruchomi metodę getCurrentPosition. Po jej wywołaniu wykorzystamy
dane o szerokości i długości geograficznej do utworzenia instancji mapy Google ze znacznikiem i oknem
informacyjnym dla współrzędnych, miasta i państwa. Miasto i państwo pochodzą z obiektu adresu Mozilli
i nie będą dostępne w innych przeglądarkach. Jeśli chcesz pokazać powiązany ze współrzędnymi adres
fizyczny w innych przeglądarkach, będziesz musiał użyć odwróconego geokodowania, które poznasz
w innym przepisie tego rozdziału.
Wyświetlanie na mapie informacji o lokalizacji przy użyciu getCurrentPosition 229

1. W kodzie z poprzedniego przepisu zmień przycisk na Pokaż mnie na mapie, a wywoływaną funkcję
na mapMe.
2. Dodaj znacznik skryptu Google Maps JavaScript API V3 Overlay (zauważ, że w przypadku wersji 3
zestawu narzędzi Google Maps nie potrzebujesz już klucza deweloperskiego):
<script src="http://maps.google.com/maps/api/js?sensor=false">
3. Zmodyfikuj wywołanie getCurrentPosition, dodając funkcję obsługi błędów:
navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler);
4. Dodaj funkcję geoErrorHandler(error), która będzie obsługiwać wszelkie błędy zwracane przez
wywołanie getCurrentPosition.
5. Popraw elementy div w obrębie elementu body, tak aby odpowiadały tym z listingu 10.2, a więc aby
elementy o identyfikatorach mapCanvas i myLocation oraz element zawierający przycisk Pokaż mnie
na mapie zostały opakowane dodatkowym elementem div.

Listing 10.2. Użycie getCurrentPosition do pokazania położenia na mapie

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>10.2. Pokaż mnie na mapie, uwzględniając obsługę błędów</title>
<style>
#container {
width:500px;
}

#mapCanvas {
width:500px;
height:300px;
border-style:solid;
border-width:2px;
margin: 22px 0;
}

#btnMapMe {
float:left;
}

#myLocation {
float:right;
}
</style>
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>

// inicjalizuj stronę
function init() {
// dodaj procedurę nasłuchu kliknięć przycisku
var btnMapMe = document.getElementById('mapMe');
btnMapMe.addEventListener('click',mapMe,false);
}

// funkcja wywołania zwrotnego w przypadku pomyślnego wykonania getCurrentPosition


function geoSuccess(position) {
230 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

// pobierz odwołanie do elementu div z wynikami


var myLocationDiv = document.getElementById('myLocation');

// pobierz współrzędne geograficzne


var posLat = position.coords.latitude;
var posLng = position.coords.longitude;
var posAccuracy = position.coords.accuracy;

// wyświetl pola współrzędnych i dokładności pozycji


myLocationDiv.innerHTML = 'Szerokość: ' + posLat + ', długość: ' + posLng + '<br>Dokładność: ' + posAccuracy;

// utwórz obiekt LatLng mapy Google na podstawie współrzędnych


var myLatlng = new google.maps.LatLng(posLat, posLng);

// ustaw opcje dla mapy, używając obiektu LatLng jako środka


var myOptions = {
zoom: 14,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}

// utwórz instancję mapy Google


var map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

// dodaj znacznik pozycji


var marker = new google.maps.Marker({
position: myLatlng,
map: map
});

// utwórz tekst do okna informacyjnego


var infoText = '';
infoText = posLat + ', ' + posLng + '<br>Dokładność: ' + posAccuracy;
if (position.address) {
infoText += '<br>' + position.address.city + ', ' + position.address.region;
}

// utwórz okno informacyjne i wstaw tekst


var infowindow = new google.maps.InfoWindow();
infowindow.setContent(infoText);
infowindow.open(map, marker);
}

// procedura obsługi błędu dla getCurrentPosition


function geoErrorHandler(error) {

// inicjalizuj komunikat błędu


var errMessage = 'BŁĄD: ';

// określ komunikaty odpowiadające parametrom kodu błędu


switch(error.code)
{
case error.PERMISSION_DENIED:
errMessage += 'Użytkownik nie udostępnił danych o położeniu geograficznym.';
break;
case error.POSITION_UNAVAILABLE:
errMessage += 'Nie mogę wykryć aktualnego położenia.';
break;
case error.TIMEOUT:
errMessage += 'Przekroczono limit czasowy uzyskiwania położenia.';
Wyświetlanie na mapie informacji o lokalizacji przy użyciu getCurrentPosition 231

break;
default:
errMessage += 'Nieznany błąd.';
break;
}

// wyświetl błąd użytkownikowi


document.getElementById('myLocation').innerHTML = errMessage;
}

// funkcja wywoływana po kliknięciu przycisku służącego do ustalenia lokalizacji


function mapMe() {
var myLocationDiv = document.getElementById('myLocation');

// sprawdź obsługę położenia geograficznego


if (navigator.geolocation) {
// wykonaj asynchroniczne wywołanie getCurrentPosition
navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler);
myLocationDiv.innerHTML = 'Uzyskiwanie informacji o Twoim położeniu...';
} else {
// Geolocation API nie jest obsługiwane
myLocationDiv.innerHTML = 'Geolocation API nie jest obsługiwane';
}
}

// inicjalizuj stronę
window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id="container">
<div id="mapCanvas"></div>
<div id="btnMapMe">
<button id="mapMe">Pokaż mnie na mapie</button>
</div>
<div id="myLocation"></div>
</div>
</body>
</html>

Po kliknięciu przycisku Pokaż mnie na mapie zostanie wywołana funkcja mapMe. Tak jak poprzednio,
sprawdzamy, czy Geolocation API jest obsługiwane — jeżeli tak, wykonywana jest metoda getCurrentPosition.
Jeśli wykonanie getCurrentPosition powiedzie się, wywoływana jest funkcja geoSuccess — współrzędne
są pobierane, wyświetlane w elemencie div o identyfikatorze myLocation, a następnie wykorzystywane do
utworzenia instancji mapy Google ze znacznikiem i oknem informacyjnym. Wynik będzie podobny do tego
z rysunku 10.2, oczywiście z uwzględnieniem Twojej lokalizacji.
W wywołaniu metody getCurrentPosition dodany jest drugi parametr, o nazwie geoErrorHandler,
będący procedurą obsługi błędu. Metody dotyczące lokalizacji zwracają trzy rodzaje błędów:
„ PERMISSION_DENIED (1) — żądanie nie powiodło się, ponieważ użytkownik nie zgodził się
na udostępnienie informacji o lokalizacji;
„ POSITION_UNAVAILABLE (2) — przeglądarka nie potrafi określić lokalizacji urządzenia;
„ TIMEOUT (3) — zwracany, jeśli został zdefiniowany i przekroczony limit czasu oczekiwania.
232 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Rysunek 10.2. Twoje położenie geograficzne pokazane na mapie Google

Procedura obsługi błędów pozwala przechwycić zwrócony błąd i podjąć odpowiednie działanie. Częsty
błąd, PERMISSION_DENIED, wynika z tego, że użytkownik nie udostępnił informacji wymaganych przez
wywołanie Geolocation API na stronie. Aby wyświetlić ten błąd, załaduj ponownie swoją stronę, a gdy
przeglądarka poprosi Cię o zgodę na udostępnienie informacji o lokalizacji, wybierz brak udostępnienia
lub nie pozwól na dostęp do Twojego położenia geograficznego. Procedura obsługi błędów geoErrorHandler
zostanie wywołana z przekazanym jej obiektem błędu lokalizacji. Obiekt błędu lokalizacji, w naszym kodzie
nazwany error, będzie zawierać dwa atrybuty: code i message. Opisany wcześniej kod jest numeryczną stałą,
która definiuje typ błędu, natomiast komunikat może zawierać skierowaną do Ciebie jako programisty
opcjonalną wiadomość tekstową wyjaśniającą przyczynę błędu. W omawianym przypadku — ze względu
na zablokowanie dostępu do informacji o lokalizacji — zostanie przekazany kod błędu PERMISSION_DENIED,
a Ty możesz wyświetlić własny komunikat.

Uwaga
Mozilla dodaje do możliwych kodów błędów czwartą wartość — UNKNOWN_ERROR (0). Błąd ten jest
zwracany, gdy pobieranie lokalizacji nie powiodło się z nieznanego, nieobjętego innymi kodami
błędów powodu. Polecenie switch na listingu 10.2 przechwyci domyślnie każdy nieznany błąd, w tym
UNKNOWN_ERROR zwracany przez Mozillę. W tym przypadku atrybut message obiektu błędu może być bardziej
przydatny dla określenia przyczyny błędu.

W tym przepisie wykorzystujemy Google Maps JavaScript API V3 Overlay do pokazania użytkownikowi
jego położenia za pomocą znacznika. Otwieramy również przypisane do tego znacznika okno informacyjne,
wyświetlające jego współrzędne geograficzne. Mozilla udostępnia dodatkowy atrybut położenia, address,
którego nie ma w specyfikacji W3C. Udostępnia on fizyczne położenie współrzędnych. Adres ten może
oczywiście być niedokładny, gdyż margines błędu lokalizacji może być zbyt duży, ale kiedy jest dostępny,
pozwala ominąć konieczność skorzystania z innej usługi w celu odwróconego geokodowania współrzędnych.
Jeżeli obiekt adresu jest dostępny, możesz wyciągnąć z niego atrybuty miasta i regionu oraz dopisać
odpowiednie wartości do okienka informacyjnego. Oto atrybuty obiektu adresowego:
„ city — DOMString zawierający miasto;
„ country — DOMString zawierający kraj;
Określanie odległości za pomocą opcji lokalizacji 233

„ countryCode — DOMString z kodem kraju;


„ county — DOMString zawierający hrabstwo;
„ postalCode — DOMString z kodem pocztowym;
„ premises — DOMString zawierający numer domu/lokalu;
„ region — DOMString zawierający region;
„ street — DOMString z nazwą ulicy;
„ streetNumber — DOMString z numerem posesji.

Jeśli obiekt adresu nie jest dostępny, możesz użyć dostarczanej przez Google usługi odwróconego
geokodowania, co zobaczysz w następnym przepisie, w którym przyjrzysz się trzeciemu parametrowi
metody getCurrentPosition i możliwościom jego wykorzystania w zależności od konkretnych potrzeb.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Określanie odległości za pomocą opcji lokalizacji
W tym przepisie wykorzystamy metodę getCurrentPosition do zlokalizowania położenia Twojej przeglądarki,
a następnie obliczenia odległości od zbioru punktów, wykonania odwróconego geokodowania Twojego
położenia geograficznego i wyświetlenia tej informacji użytkownikowi. Aby lepiej sterować dostarczonymi
informacjami o lokalizacji, wykorzystamy trzeci parametr metody getCurrentPosition, czyli opcje lokalizacji.
Opcje lokalizacji są obiektem przekazywanym metodzie getCurrentPosition jako parametr i pozwalają
w pewnym stopniu sterować zachowaniem metody. Może to być przydatne w zależności od rodzaju aplikacji,
z którą pracujesz. Jeśli pracujesz na przykład nad wykorzystującą informacje o lokalizacji i przeznaczoną na
rynek urządzeń przenośnych aplikacją dotyczącą restauracji, standardowa dokładność zwracanej informacji
o położeniu geograficznym może być dla Ciebie niewystarczająca. W obiekcie opcji lokalizacji metody
getCurrentPosition możesz wybrać do trzech opcji, jak pokazano w tabeli 10.3.

Tabela 10.3. Opcje lokalizacji

Atrybut Wartość domyślna Opis


enableHighAccuracy false, nie jest Wartość logiczna; ustawienie true mówi przeglądarce, że
włączona chcesz uzyskać najdokładniejsze informacje o lokalizacji, jakie
urządzenie jest w stanie dostarczyć. Zależnie od używanego
urządzenia zwrócona informacja może być taka sama jak
w przypadku podania wartości false. Atrybut jest opcjonalny.
maximumAge 0, dozwolona dowolna Maksymalny okres ważności zwróconej informacji o lokalizacji
w milisekundach. Przeglądarka może lokalnie przechowywać
ostatnią informację o lokalizacji, aby np. oszczędzać baterie.
Atrybut jest opcjonalny.
timeout 0, brak limitu czasu Maksymalny czas (w milisekundach), jaki może upłynąć
do momentu zwrócenia przez przeglądarkę informacji
o lokalizacji. Atrybut jest opcjonalny.
234 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Uwaga
W przypadku opcji timeout do limitu czasu nie jest wliczany czas oczekiwania na zezwolenie
użytkownika na dostęp do informacji o lokalizacji, gdy został wyświetlony odpowiedni panel
lub okno dialogowe. Liczony jest wyłącznie czas wykonania właściwego wywołania.

Po wczytaniu strony przeglądarka wywoła funkcję setLocation, która z kolei wywoła metodę
getCurrentPosition z zestawem opcji. Po wywołaniu funkcji zwrotnej wykorzystamy współrzędne
geograficzne do utworzenia instancji mapy Google, wykonania odwróconego geokodowania i obliczenia
odległości do różnych miast.
1. Dodaj wywołanie metody setLocation do atrybutu onload elementu body oraz funkcję
setLocation, przekazując w wywołaniu getCurrentPosition obiekt opcji lokalizacji.
2. Zaktualizuj znacznik skryptu Google Maps JavaScript API V3 Overlay, aby załadować bibliotekę
geometry, wykorzystywaną do obliczenia odległości:
<script src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry">
3. Dodaj funkcję reverseGeoCode, która przyjmuje współrzędne geograficzne i pobiera informacje
adresowe z geokodera Google.
4. Dodaj funkcję calculateDistance, która używa metody computeDistanceBetween do obliczenia
odległości do Warszawy, Gdańska i Krakowa.
5. Popraw elementy div w obrębie elementu body, tak aby odpowiadały tym z listingu 10.3, a więc
aby w kontenerze div znalazły się: element div o identyfikatorze mapCanvas, informacje o położeniu
geograficznym oraz elementy div z odległościami do miast.

Listing 10.3. getCurrentPosition z opcjami lokalizacji

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>10.3. Oblicz odległości</title>
<style>
#container {
width:500px;
}

#mapCanvas {
width:500px;
height:300px;
border-style:solid;
border-width:2px;
margin: 22px 0;
}

#location {
float:right;
text-align:right;
}

#cityDistance tr:nth-child(odd) { background-color:#eee; }


#cityDistance tr:nth-child(even) { background-color:#fff; }

.numDistance {
text-align:right;
Określanie odległości za pomocą opcji lokalizacji 235

}
</style>
<script src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script>
// zmienna globalna
var map;

// funkcja wywołania zwrotnego w przypadku pomyślnego wykonania getCurrentPosition


function geoSuccess(position) {

// pobierz współrzędne geograficzne


var myPosLat = position.coords.latitude;
var myPosLng = position.coords.longitude;

// wyświetl pola współrzędnych


document.getElementById('myPosLat').innerHTML = myPosLat;
document.getElementById('myPosLng').innerHTML = myPosLng;

// utwórz obiekt LatLng


var myLatLng = new google.maps.LatLng(myPosLat, myPosLng);

// ustaw opcje dla mapy i utwórz mapę


var myOptions = {
zoom: 14,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

// odwrócone geokodowanie szerokości i długości geograficznej


reverseGeoCode(myLatLng);

// oblicz odległość do interesujących Cię punktów


calculateDistance(myLatLng);

// aktualizuj status
document.getElementById('geoStatus').innerHTML = 'Uzyskano informację o położeniu';
}

// funkcja do odwróconego geokodowania przy danych szerokości i długości geograficznej


function reverseGeoCode(geoLatLng) {

// utwórz instancje obiektów


var geocoder = new google.maps.Geocoder();
var infowindow = new google.maps.InfoWindow();

// wykonaj geokodowanie
geocoder.geocode({'latLng': geoLatLng}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
// sprawdź, czy otrzymaliśmy adres
if (results[0]) {
// utwórz znacznik na mapie
var marker = new google.maps.Marker({
position: geoLatLng,
map: map
});
// ustaw adres jako treść i otwórz okno
infowindow.setContent(results[0].formatted_address);
infowindow.open(map, marker);
}
236 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

} else {
alert('Geokodowanie nie powiodło się z powodu: ' + status);
}
});
}

// funkcja obliczająca odległość


function calculateDistance(disLatLng) {

// skonfiguruj zmienne i obiekty dotyczące odległości


var conEarth = 6371; // średni promień Ziemi w kilometrach
var gmapsSpherLib = google.maps.geometry.spherical;

// interesujące Cię punkty


var WARLatLng = new google.maps.LatLng(52.229676,21.012229);
var WROLatLng = new google.maps.LatLng(51.107885,17.038538);
var KRALatLng = new google.maps.LatLng(50.06465,19.94498);

// obliczenia odległości
var distFromWAR = gmapsSpherLib.computeDistanceBetween(disLatLng,WARLatLng,conEarth).toFixed(2);
var distFromWRO = gmapsSpherLib.computeDistanceBetween(disLatLng,WROLatLng,conEarth).toFixed(2);
var distFromKRA = gmapsSpherLib.computeDistanceBetween(disLatLng,KRALatLng,conEarth).toFixed(2);

// ustaw wartości do wyświetlenia


document.getElementById('divDistFromWAR').innerHTML = distFromWAR + ' km';
document.getElementById('divDistFromWRO').innerHTML = distFromWRO + ' km';
document.getElementById('divDistFromKRA').innerHTML = distFromKRA + ' km';
}

// procedura obsługi błędu dla getCurrentPosition


function geoErrorHandler(error) {

// inicjalizuj komunikat błędu


var errMessage = 'BŁĄD: ';

// określ komunikaty odpowiadające parametrom kodu błędu


switch(error.code)
{
case error.PERMISSION_DENIED:
errMessage += 'Użytkownik nie udostępnił danych o położeniu geograficznym.';
break;
case error.POSITION_UNAVAILABLE:
errMessage += 'Nie mogę wykryć aktualnego położenia.';
break;
case error.TIMEOUT:
errMessage += 'Uzyskiwanie położenia przekroczyło limit czasowy.';
break;
default:
errMessage += 'Nieznany błąd.';
break;
}

// wyświetl błąd użytkownikowi


document.getElementById('geoStatus').innerHTML = errMessage;
}

// funkcja inicjalizująca wywołanie dotyczące położenia


function setLocation() {

var divStatus = document.getElementById('geoStatus');


Określanie odległości za pomocą opcji lokalizacji 237

// sprawdź obsługę położenia geograficznego


if (navigator.geolocation) {

// maksymalny okres ważności informacji to 1 minuta, a limitu czasu — 30 sekund


var posOptions = {maximumAge:60000,
timeout:30000};

// wykonaj asynchroniczne wywołanie getCurrentPosition


navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler, posOptions);
divStatus.innerHTML = 'Pozyskiwanie Twojego położenia.';

} else {
// Geolocation API nie jest obsługiwane
divStatus.innerHTML = 'Geolocation API nie jest obsługiwane';
}
}

// uruchom pozyskiwanie informacji o lokalizacji


window.addEventListener('load',setLocation,false);

</script>
</head>
<body>
<div id="container">
<div id="mapCanvas"></div>
<div id="location">
<table id="status">
<tr>
<td colspan="2"><div id="geoStatus"></div></td>
</tr>
<tr>
<td>Szerokość geograficzna:</td>
<td class="numDistance"><div id="myPosLat"></div></td>
</tr>
<tr>
<td>Długość geograficzna:</td>
<td class="numDistance"><div id="myPosLng"></div></td>
</tr>
</table>
</div>
<div id="distance">
<table id="cityDistance">
<tr>
<td>Warszawa:</td>
<td class="numDistance"><div id="divDistFromWAR"></div></td>
</tr>
<tr>
<td>Wrocław:</td>
<td class="numDistance"><div id="divDistFromWRO"></div></td>
</tr>
<tr>
<td>Kraków:</td>
<td class="numDistance"><div id="divDistFromKRA"></div></td>
</tr>
</table>
</div>
</div>
</body>
</html>
238 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Po załadowaniu strony do pobrania współrzędnych geograficznych jest wykorzystywana metoda


getCurrentPosition, której wywołanie zawiera jednak kilka istotnych opcji. Zostaje utworzony obiekt
o nazwie posOptions, który jest następnie przekazywany do getCurrentPosition. W obiekcie posOptions
maksymalny okres ważności informacji o położeniu jest ustawiony na 60 000, co odpowiada jednej minucie,
dzięki czemu getCurrentPosition może skorzystać z wcześniej zbuforowanej informacji o położeniu
geograficznym tylko wówczas, gdy nie upłynęła jeszcze jedna minuta od momentu jej uzyskania. Limit czasu
jest ustawiony na 30 000 (30 sekund) — stanowi on ograniczenie czasu, który getCurrentPosition może
przeznaczyć na pobranie informacji o lokalizacji:
var posOptions = {maximumAge:60000, timeout:30000};

Po uzyskaniu informacji o położeniu geograficznym wykonywane jest odwrócone geokodowanie, które


za pomocą poręcznego obiektu geocoder pozwala uzyskać ze skryptu Google pełen adres. Następnie jest
obliczana odległość do trzech miast — rezultat będzie wyglądał podobnie do tego zaprezentowanego na
rysunku 10.3.

Rysunek 10.3. Obliczanie odległości od trzech miast

Jak widzisz, możesz sterować pobieraniem informacji o położeniu geograficznym poprzez ustawienia
obiektu opcji lokalizacji w wywołaniu getCurrentPosition. Opcje pozwalają Ci wpływać na dokładność
i wydajność poprzez dostosowanie Twojej aplikacji do wymagań użytkownika. Do tego momentu
zajmowaliśmy się mapami, odwróconym geokodowaniem, a nawet obliczaniem odległości, ale prawdopodobnie
przyszła Ci do głowy myśl o urządzeniach przenośnych i aktualizowaniu informacji prezentowanych
użytkownikowi wraz ze zmianą jego położenia geograficznego. Cóż, Geolocation API umożliwia realizację
także tego zadania, o czym przekonasz się w kolejnym przepisie.
Podążanie za poruszającym się obiektem dzięki watchPosition 239

PRZEPIS DLA ZAAWANSOWANYCH


Podążanie za poruszającym się obiektem
dzięki watchPosition
W wielu przypadkach przeglądarka używana przez odwiedzającego Twoją stronę będzie się znajdować
na urządzeniu przenośnym. Widok ludzi idących ulicą, jadących metrem albo autobusem i jednocześnie
pobierających informacje o swoim otoczeniu albo usytuowaniu nie jest już rzadkością. Metoda
getCurrentPosition dostarcza obiekt lokalizacji jednorazowo, kiedy zostanie wywołana. Miło byłoby
jednak „śledzić” położenie osoby, która się porusza. W takim przypadku przydatne są dwie nowe metody
Geolocation API: watchPosition i clearWatch.
Metoda watchPosition przypomina metodę getCurrentPosition i przyjmuje takie same parametry.
Kiedy zostanie ona wywołana, przeglądarka utworzy zadanie działające w tle i zwróci jego identyfikator.
Zadanie będzie pozyskiwać informacje o aktualnej lokalizacji, przekazywać je do funkcji wywołania zwrotnego,
a następnie ustawiać zegar dotyczący kolejnego sprawdzania położenia. Za każdym razem, kiedy aktywowany
jest zegar i uzyskiwana informacja o nowym położeniu, jest ono porównywane z wcześniejszym pod kątem
„znaczącej” różnicy. Jeśli nowe położenie znacząco różni się od ostatniego, aktywowana jest funkcja
wywołania zwrotnego, do której trafia informacja o nowej lokalizacji. Proces ten będzie powtarzany, dopóki
nie zostanie wywołana metoda clearWatch z identyfikatorem obserwacji jako parametrem albo nie zostaną
zamknięte zakładka lub okno przeglądarki (na platformach przenośnych, takich jak iPhone, podobny efekt
może spowodować przejście przeglądarki do pracy w tle). Oto składnia metod watchPosition i clearWatch:

long watchPosition (udaneWywołanie [, nieudaneWywołanie] [, opcjeLokalizacji])

Parametry metody watchPosition są następujące:


„ udaneWywołanie — funkcja, która jest wykonywana, kiedy nowe położenie geograficzne
zostanie określone przez przeglądarkę, i której przekazywany jest obiekt lokalizacji;
„ nieudaneWywołanie — (opcjonalny) funkcja do obsługi wszelkich błędów, które wystąpiły;
„ opcjeLokalizacji — (opcjonalny) obiekt opcji pobierania informacji o położeniu
geograficznym.

clearWatch (watchId)

A oto parametr metody clearWatch:


„ watchId — identyfikator (typu long) odwołania do procesu obserwacji, który należy zakończyć.

W bieżącym przepisie wykorzystamy metodę watchPosition do pobierania informacji o położeniu


geograficznym przeglądarki i pokazania na mapie Google nowego znacznika, ilekroć położenie to różni
się od ostatniego znacznika o więcej niż ćwierć kilometra. Aby przepis można było łatwo uruchomić na
iPhonie i pokazać przemieszczanie się na mapie, obszar wyświetlania został w znaczniku meta ustawiony
dla szerokości ekranu iPhone’a; odpowiednio dostosowane zostały także style CSS. Punkty zostaną połączone
linią pokazującą przebytą drogę, a mapa będzie wypośrodkowywana na ostatnim pokazanym punkcie.
Kiedy użytkownik kliknie przycisk Zakończ, proces obserwacji zostanie zakończony.
240 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

Sprawdzanie odległości od ostatniego punktu pozwala Ci utrzymywać dość przejrzysty wygląd mapy,
w miarę jak dana osoba porusza się ze swoim urządzeniem przenośnym. Jeśli jednak miałbyś do czynienia
z mniejszym obszarem ruchu, takim jak centrum miasta, różnica jednej czwartej kilometra może być zbyt
duża; jeśli z kolei poruszałbyś się szybkim środkiem transportu, jedna czwarta kilometra może nie być
dostatecznie dużą wielkością. Sprawdzanie różnicy powinno się zatem opierać na konkretnych potrzebach.
W tym przepisie nauczysz się, jak w miarę zmian położenia geograficznego filtrować kolejne punkty
reprezentujące lokalizację, wykorzystując obliczenia odległości między punktami.
1. Utwórz stronę HTML z przyciskami Rozpocznij i Zakończ, jak pokazano na listingu 10.4.
2. Dodaj skrypt dla Google Maps JavaScript API V3 Overlay z uwzględnieniem biblioteki geometry
oraz zmienne globalne, które będą przechowywać identyfikator obserwacji, mapę, linię łamaną
i ostatnie wartości szerokości oraz długości geograficznej.
3. Dodaj w znaczniku script funkcję initMap i ustaw zdarzenie load, aby ją uruchomić.
4. Dodaj funkcje startWatch i clearWatch.
5. Dodaj funkcje successCallback i errorCallback.

Listing 10.4. Użycie watchPosition do śledzenia ścieżki przemieszczania

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<title>10.4. Pozostawianie śladu</title>
<style>
#container {
width:300px;
}

#mapCanvas {
width:300px;
height:200px;
border-style:solid;
border-width:2px;
margin: 22px 0;
}

#btnMap {
float:left;
}

#location {
float:right;
}

.numDistance {
text-align:right;
}
</style>
<script type="text/javascript"
´src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script>
Podążanie za poruszającym się obiektem dzięki watchPosition 241

// zadeklaruj zmienne
var watchId; // identyfikator procesu śledzenia położenia geograficznego
var map; // mapa
var poly; // linia łamana ścieżki
var lastLatLng; // ostatnie wartości szerokości i długości geograficznych

// ustaw stałą dla metody computeDistanceBetween z kilometrem jako jednostką


var conEarthKm = 6371;

// inicjalizuj mapę
function initMap() {

// dodaj procedury nasłuchu przycisków


var btnStartWatch = document.getElementById('startWatch');
var btnStopWatch = document.getElementById('stopWatch');
btnStartWatch.addEventListener('click',startWatch,false);
btnStopWatch.addEventListener('click',stopWatch,false);

// ustaw początkowe położenie w Poznaniu i utwórz mapę


lastLatLng = new google.maps.LatLng(52.405548,16.931251); // Poznań
var myOptions = {
zoom: 14,
center: lastLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

// ustaw linię łamaną do pokazywania ścieżki


var polyOptions = {
strokeColor: '#00FF00',
strokeOpacity: 1.0,
strokeWeight: 3
}
poly = new google.maps.Polyline(polyOptions);
poly.setMap(map);
}

// funkcja wywołania zwrotnego do śledzenia położenia


function successCallback(position) {

// pobierz współrzędne szerokości i długości geograficznej


var posLat = position.coords.latitude;
var posLng = position.coords.longitude;

// utwórz nowy obiekt LatLng map Google


var newLatLng = new google.maps.LatLng(posLat,posLng);

// oblicz odległość od ostatniego punktu


var distFromLast = google.maps.geometry.spherical.computeDistanceBetween(newLatLng, lastLatLng, conEarthKm);

// sprawdź, czy odległość jest większa niż jedna czwarta kilometra


if (distFromLast > 0.25) {

// pobierz tablicę ścieżki linii łamanej


var path = poly.getPath();

// dodaj nowe współrzędne do tablicy ścieżki


path.push(newLatLng);
242 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

// dodaj znacznik o nowych współrzędnych


var marker = new google.maps.Marker({
position: newLatLng,
title: '#' + path.getLength(),
map: map
});

// wypośrodkuj mapę na nowych współrzędnych


map.setCenter(newLatLng);

// aktualizuj ekran
document.getElementById('myPosLat').innerHTML = posLat.toFixed(8);
document.getElementById('myPosLng').innerHTML = posLng.toFixed(8);
document.getElementById('watchStatus').innerHTML = 'Zaktualizowane położenie (#' + path.getLength() + ')';

// ustaw nowe współrzędne jako ostatnie współrzędne


lastLatLng = newLatLng;

}
}

// procedura obsługi błędu dla śledzenia położenia geograficznego


function errorCallback(error) {

// inicjalizuj komunikat błędu


var errMessage = 'BŁĄD: ';
var divWatchStatus = document.getElementById('watchStatus');

// określ komunikaty odpowiadające parametrom kodu błędu


switch(error.code)
{
case error.PERMISSION_DENIED:
errMessage += 'Użytkownik nie udostępnił danych o położeniu geograficznym.';
break;
case error.POSITION_UNAVAILABLE:
errMessage += 'Nie mogę wykryć aktualnego położenia.';
break;
case error.TIMEOUT:
errMessage += 'Uzyskiwanie położenia przekroczyło limit czasowy.';
break;
default:
errMessage += 'Nieznany błąd.';
break;
}

// aktualizuj status
divWatchStatus.innerHTML = errMessage;
}

// obsługa przycisku rozpoczęcia obserwacji


function startWatch() {

var divWatchStatus = document.getElementById('watchStatus');

// sprawdź, czy obsługa położenia geograficznego jest dostępna


if (navigator.geolocation) {

// upewnij się, że jest tylko jedna obserwacja


if (watchId == null) {
Podążanie za poruszającym się obiektem dzięki watchPosition 243

// ustaw opcje położenia


// maksymalny okres ważności informacji o lokalizacji 40 sekund
// limit czasu 20 sekund
// zwiększona dokładność włączona dla urządzenia przenośnego
var posOptions = {maximumAge:40000,
timeout:20000,
enhancedAccuracy:true}

// rozpocznij obserwację
watchId = navigator.geolocation.watchPosition(successCallback,
errorCallback,
posOptions);

// aktualizuj status
divWatchStatus.innerHTML = 'Obserwacja położenia ('+watchId+')';
}
} else {
// aktualizuj status mówiący, że uzyskiwanie informacji o położeniu geograficznym nie jest obsługiwane
divWatchStatus.innerHTML = 'Uzyskiwanie informacji o położeniu geograficznym nie jest obsługiwane';
}
}

// obsługa przycisku zatrzymania obserwacji


function stopWatch() {

// sprawdź, czy aktualnie trwa obserwacja


if (watchId != null) {
// zatrzymaj obserwację
navigator.geolocation.clearWatch(watchId);

// ustaw flagę watchId na null


watchId = null;

// aktualizuj status
document.getElementById('watchStatus').innerHTML = 'Wyłączona';
}
}

// inicjalizuj stronę
window.addEventListener('load',initMap,false);

</script>
</head>
<body>
<div id="container">
<div id="mapCanvas"></div>
<div id="btnMap">
<button id="startWatch">Rozpocznij</button>
<br>
<button id="stopWatch">Zakończ</button>
</div>
<div id="location">
<table id="status">
<tr>
<td>Szerokość:</td>
<td class="numDistance"><div id="myPosLat"></div></td>
</tr>
<tr>
244 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym

<td>Długość:</td>
<td class="numDistance"><div id="myPosLng"></div></td>
</tr>
<tr>
<td colspan="2"><div id="watchStatus"></div></td>
</tr>
</table>
</div>
</div>
</body>
</html>

Przyjrzyjmy się listingowi 10.4 nieco dokładniej. Po wczytaniu strony jest wywoływana funkcja initMap,
która inicjalizuje mapę na stronie. Wybraliśmy współrzędne w Poznaniu, ale mógłbyś ustawić inny
początkowy punkt środka mapy lub załadować mapę dopiero wtedy, gdy obserwacja się rozpocznie.
W initMap tworzysz warstwę linii łamanej nad mapą, co pozwoli Ci połączyć współrzędne z obserwacji
i wyświetlić „ścieżkę” poruszania się obserwatora. Kiedy mapa jest gotowa, możesz rozpocząć obserwację
położenia geograficznego.
Aby rozpocząć, należy kliknąć lub dotknąć przycisk Rozpocznij. Jak zwykle powinieneś zostać poproszony
o zgodę na udostępnienie informacji o Twojej lokalizacji. Po udzieleniu zezwolenia zostanie wywołana
funkcja startWatch, za pomocą której poprzez sprawdzenie zmiennej globalnej identyfikatora obserwacji
upewnisz się, że proces obserwacji nie został rozpoczęty wcześniej — zapobiega to uruchomieniu więcej niż
jednego takiego procesu. Następnie są ustawiane opcje lokalizacji i wywoływana jest metoda watchPosition.
Identyfikator obserwacji zwracany przez watchPosition jest następnie zapisywany w zmiennej watchId, tak
aby można jej było użyć później do zatrzymania procesu za pomocą clearWatch.
Kiedy watchPosition zwraca obiekt lokalizacji, pobierane są z niego właściwości szerokości
i długości geograficznej, tworzony jest reprezentujący je obiekt, a następnie za pomocą metody Google
computeDistanceBetween jest obliczana odległość od ostatnich znanych współrzędnych geograficznych.
W tym przypadku przekazujesz metodzie nowe współrzędne, ostatnie współrzędne i stałą wartość,
informującą funkcję, że chcesz uzyskać wynik w kilometrach. Następnie sprawdzamy, czy odległość jest
większa niż ćwierć kilometra, tak aby nie zalać mapy znacznikami. Jeśli następny punkt jest położony
w większej odległości, dodajemy nowy znacznik, a do tablicy punktów linii łamanej ścieżki dopisujemy
nowe współrzędne. Dopisanie współrzędnych do tablicy aktualizuje linię na mapie, więc z każdym nowym
punktem ścieżka będzie rosła, pokazując, gdzie byłeś, jak zaprezentowano na rysunku 10.4.
Ten przepis pokazuje, w jaki sposób uzyskać dostęp do informacji o położeniu geograficznym
przeglądarki i być powiadamianym o każdej jego zmianie. Informacja ta może być użyta w wielu
aplikacjach, wliczając integrację z bazami danych usług opartych o lokalizację, takimi jak Yahoo Query
Language i Miejsca Google, pokazujących znajdujące się w pobliżu atrakcje.

Uwaga
Podczas pracy z urządzeniami, szczególnie przenośnymi, i pozyskiwaniem informacji o położeniu
geograficznym warto pamiętać o tym, że pobieranie informacji o urządzeniu przyspiesza zużycie
jego baterii. W celu zminimalizowania tego zużycia warto zaprojektować aplikację tak, aby pobierać
informacje tylko w razie potrzeby.
Podsumowanie 245

Rysunek 10.4. Budowanie ścieżki za pomocą punktów współrzędnych

Podsumowanie
Geolocation API udostępnia prosty interfejs do dodawania do witryn i aplikacji funkcji związanych
z miejscem położenia i wykorzystujących informacje o nim. Oto niektóre z rozwiązań, które mogą zostać
zaprojektowane:
„ wyświetlanie informacji związanych z lokalizacją,
„ wykorzystanie informacji o przebywaniu w pobliżu czegoś lub kogoś,
„ dynamiczne dostosowywanie ustawień lokalnych, takich jak język i waluta,
„ integracja z mapami i trasami,
„ oznaczanie danych, zdjęć i innych rzeczy informacjami o lokalizacji.

W tym rozdziale poznaliśmy metody getCurrentPosition, watchPosition i clearWatch wraz z funkcjami


wywołania zwrotnego z tych metod. Możliwości wydają się nieograniczone, a ich dostępność w przeglądarkach
stale wzrasta.
246 Rozdział 10. Wykorzystanie Geolocation API do uzyskania informacji o położeniu geograficznym
11
Przechowywanie danych
po stronie klienta

P rzechowywanie informacji w przeglądarce użytkownika było dotychczas ograniczone do kluczy i wartości


zapisywanych w ciasteczkach. Zapisywanie ciasteczek wiąże się jednak z restrykcjami odnośnie do ich
rozmiaru i struktury, a ponadto z każdym żądaniem są one przesyłane do właściwych witryn, co generuje
zbędne obciążenie sieci. Pewne zbiory danych mogłyby usprawnić działanie z punktu widzenia użytkownika,
gdyby ciasteczka były nie tylko przechowywane, ale i pobierane lokalnie zamiast za pośrednictwem serwerów
WWW. W HTML5 zostały dodane dwie opcje związane z przechowywaniem danych po stronie klienta:
sieciowe przechowywanie danych, obejmujące pamięć sesji i lokalną, oraz przechowywanie w bazie danych.
W tym rozdziale zapoznasz się z tymi dwiema nowymi możliwościami oraz zrealizujesz kilka przepisów,
dzięki którym dowiesz się, jak przechowywać dane lokalnie w przeglądarce.

Przechowywanie danych po stronie klienta — przegląd


Rozpoczynając rozważania na temat nowego API przechowywania danych, spójrzmy najpierw na dotychczas
dostępne w przeglądarkach rozwiązania dotyczące lokalnego przechowywania informacji. Zwykle w celu
przechowywania informacji, które mogłyby być pobierane w późniejszym czasie przez przeglądarkę użytkownika,
musiałeś utworzyć ciasteczko. Przechowywane informacje mogły zawierać na przykład preferencje użytkownika,
dane z formularza czy klucze użytkownika. Rozmiar przechowywanych ciasteczek jest jednak ograniczony
do około 4 kB, a same ciasteczka pozwalają przechowywać jedynie proste pary klucz-wartość. Ponadto
przesyłanie ciasteczka z każdym żądaniem do serwera generuje dodatkowe obciążenie sieci.
Udostępnione w HTML5 funkcje pamięci sesyjnej i lokalnej przypominają ciasteczka o tyle, że ich struktura
również ma format klucz-wartość, w którym wartości w postaci tekstu mogą być przypisywane do opartych
o tekst kluczy. W sesyjnej albo lokalnej pamięci do wartości można się odwołać przez zapytanie obiektu
przechowującego o wartość odpowiadającą odpowiedniemu kluczowi. Funkcje sesyjna i lokalna różnią się
jedynie zakresem obiektu w sesji odwiedzającego. W przechowywaniu sesyjnym dane są przechowywane
tylko dla konkretnej sesji z witryną. Po zakończeniu tej sesji — zamknięciu okna lub karty w przeglądarce
— dane są usuwane, a przy kolejnym nawiązaniu połączenia przez użytkownika pamięć sesji jest pusta.
Jeśli natomiast chcesz, aby te same dane były dostępne w kolejnych sesjach — czy to w tym samym czasie
w innym oknie, czy też w nowej sesji w późniejszym terminie — możesz użyć zapisu lokalnego, który
przechowuje dane nawet po zamknięciu bieżącej sesji.
248 Rozdział 11. Przechowywanie danych po stronie klienta

Pary klucz-wartość ograniczają (a przynajmniej utrudniają) zapisywanie bardziej złożonych informacji.


W związku z tym został dodany trzeci rodzaj zapisu, który umożliwia przechowywanie danych w formie,
jakiej zwykle używałbyś po stronie serwera — to zapis w bazie danych, wykorzystujący bazę danych SQLite
albo IndexedDB. Pozwala on przechowywać bardziej złożone obiekty danych, co zobaczysz w dalszej części
rozdziału. Podobnie jak przy składowaniu lokalnym, dane zapisane w bazie danych nie są usuwane po
zamknięciu sesji. To, jakiego mechanizmu składowania użyjesz w swojej witrynie lub aplikacji, zależy od
rodzaju informacji, które potrzebujesz zapisać, zakresu danych oraz planowanego czasu ich przechowywania.
Tabela 11.1 pozwala szybko określić odpowiedni rodzaj zapisu.

Tabela 11.1. Rodzaje składowania po stronie klienta i ich własności

Składowanie Format Zakres Trwałość


sesyjne klucz-wartość pojedyncza sesja pojedyncza sesja
lokalne klucz-wartość kolejne sesje kolejne sesje
bazodanowe ustrukturyzowane kolejne sesje kolejne sesje

Bezpieczeństwo danych
Jeśli mówimy o przechowywaniu danych, musimy również rozważyć kwestię ich bezpieczeństwa. Tak jak
w przypadku przechowywania informacji w bazie danych serwera aplikacji internetowej, do przechowywania
w bazie danych po stronie klienta powinny być stosowane odpowiednie reguły dotyczące bezpieczeństwa.
To szczególnie istotne, ponieważ w odróżnieniu od serwera, na którym możesz kontrolować zapory ogniowe,
użytkowników, hasła czy inne aspekty związane z bezpieczeństwem, przeglądarka użytkownika znajduje
się poza kontrolowaną przez Ciebie siecią. A zatem kwestia tego, co i w jaki sposób jest przechowywane
w przeglądarce klienta, staje się o wiele ważniejsza. W zależności od danych, które zapisujesz, możesz wziąć
pod uwagę szyfrowanie, ale jeśli rozważasz szyfrowanie danych, warto w pierwszej kolejności ponownie
przyjrzeć się ich przechowywaniu.
Funkcje przechowywania danych w HTML5 realizują „opartą o pochodzenie” politykę bezpieczeństwa
poprzez ograniczanie dostępu do danych zapisanych sesyjnie, lokalnie i w bazie danych po stronie klienta
do stron pochodzących z tej samej domeny, w której zapisano dane. Dzięki temu strony z innych witryn
lub aplikacji nie mogą uzyskać dostępu do danych. Ponieważ implementacja bezpieczeństwa w oparciu
o „pochodzenie” łączy się z wykorzystaniem domeny strony jako czynnika decydującego o dostępie do sesyjnie
i lokalnie składowanych list par klucz-wartość, istnieje potencjalna możliwość narażenia przechowywanych
danych na działanie wbudowanych skryptów. Istotna jest zatem znajomość działań podejmowanych przez
zewnętrzne pliki, których używasz na swoich stronach.

Uwaga
Dane, które przechowujesz przy użyciu obiektów lokalnych, sesyjnych albo bazodanowych, są ograniczone
do konkretnej przeglądarki używanej w danej chwili przez odwiedzającego. Jeśli użytkownik powróci do
Twojej witryny lub aplikacji za pośrednictwem innej przeglądarki albo innego komputera, to przechowywane
dane nie będą dostępne. Aby dane były ogólnie dostępne, musiałbyś przechowywać je na swoim serwerze
bazy danych lub w chmurze.

Przechowywanie danych po stronie klienta jest na bieżąco implementowane w kolejnych wersjach


przeglądarek, a liderem w tej dziedzinie jest Chrome. Nadal trwa debata na temat implementacji najlepszego
Przechowywanie danych po stronie klienta — przegląd 249

mechanizmu przechowywania danych w bazie danych przez różne przeglądarki. Tabela 11.2 przedstawia
możliwości przeglądarek w zakresie przechowywania obiektów danych po stronie klienta.

Tabela 11.2. Dostępność przechowywania danych po stronie klienta w przeglądarkach

Android 2.1+
Chrome 10.0+
Firefox 10.6+
Internet Explorer 8.0+
iOS Safari 3.2+
Opera 10.6+
Safari 4.0+

Klucze i wartości — sessionStorage i localStorage


Obiekty zapisu sesyjnego i lokalnego są w swoich implementacjach podobne i różnią się jedynie zakresem
oraz trwałością zapisywanych danych, co pokazano w tabeli 11.1. Przeglądarka zapewnia wbudowany
interfejs dla obu sposobów zapisywania danych, sessionStorage oraz localStorage, a każdy z nich używa
do przechowywania danych listy par klucz-wartość. Możesz ustawić wartość dla klucza, a następnie pobrać
tę wartość, podając klucz. Oba obiekty zapisu danych udostępniają metody do ustawiania wartości, pobierania
ich, usuwania klucza, pobierania klucza dla pozycji na liście i czyszczenia wszystkich par klucz-wartość
w obiekcie zapisu. Metody i właściwości każdego rodzaju obiektu zapisu są takie same, ponieważ są one
dziedziczone z tego samego interfejsu Storage, zdefiniowanego w specyfikacji Web Storage:
„ setItem(klucz,wartość) — ustawia parę klucz-wartość przekazaną jako parametry.
Jeśli klucz istnieje, wartość jest zastępowana przekazaną wartością.
„ getItem(klucz) — zwraca wartość dla klucza przekazanego jako parametr.
„ removeItem(klucz) — usuwa parę klucz-wartość zdefiniowaną przez podany klucz.
„ key(n) — zwraca nazwę klucza odpowiadającą podanemu indeksowi.
„ clear — usuwa wszystkie pary klucz-wartość.
„ length — podaje liczbę par klucz-wartość na przechowywanej liście.

Aby ustawić element, wywołaj setItem i podaj klucz oraz wartość, które należy ustawić:
setItem(klucz, wartość)

Oto parametry:
„ klucz — klucz, pod jakim należy zapisać wartość mającą postać tekstu,
„ wartość — wartość, która ma być zapisana.

Wartość jest ciągiem znaków, więc jeśli zapisujesz liczbę, będzie ona traktowana w pamięci jak tekst i po
pobraniu należy ją przekształcić z powrotem do właściwego typu danych. Klucz i wartość zostaną następnie
zapisane w przypadkowej kolejności na liście par klucz-wartość w pamięci przeglądarki dla domeny, z której
strona została załadowana.
250 Rozdział 11. Przechowywanie danych po stronie klienta

Jeśli zapiszesz element z kluczem, który już istnieje, wartość dla tego klucza będzie zaktualizowana nowo
podaną wartością. A zatem metoda setItem działa zarówno jako metoda tworząca, jak i aktualizująca. Aby
zapisać w magazynie sesji wartość book pod kluczem source, mógłbyś wykonać w JavaScripcie następujące
polecenie:
sessionStorage.setItem('source', 'book');

Metoda setItem nie zwraca wartości, więc żeby ochronić się przed ewentualnymi błędami zapisu, możesz
zamknąć wywołanie w bloku try-catch.
Aby pobrać z pamięci sesji albo z pamięci lokalnej wartość dla klucza, wywołaj metodę getItem z kluczem,
który Cię interesuje. Zwrócona wartość będzie miała postać tekstu, którego możesz następnie użyć w swoim
skrypcie:
string getItem(klucz)

Klucz jest tutaj ciągiem znakowym, dla którego jest pobierana odpowiednia wartość.
Aby pobrać wartość zapisaną w poprzednim przykładzie, odwołaj się do odpowiadającej kluczowi source
wartości z pamięci sesji, wykonując następujące polecenie:
textSource = sessionStorage.getItem('source');

Ponieważ zarówno pamięć sesyjna, jak i lokalna są oparte o obiekty przechowywania danych, w celu
odwołania się do przechowywanych wartości kluczy możesz również wykorzystać notację obiektową z kropką,
używając klucza jako nazwy właściwości, tak jak poniżej:
textSource = sessionStorage.source;

Teraz, po omówieniu metod get i set pamięci sesyjnej i lokalnej, przyjrzyjmy się działaniu podstawowych
przepisów. Zaczniemy od przepisów dotyczących obiektu sesji, a następnie przejdziemy do przechowywania
lokalnego.

Wskazówka
W przeglądarkach takich jak Firefox użytkownik może wyłączyć przechowywanie danych. Aby upewnić
się, że przechowywanie danych jest dostępne, Twój kod powinien zapisać, a następnie spróbować
pobrać jakąś wartość. Jeśli wartość ta nie może zostać odzyskana, to przechowywanie danych jest
być może w danej przeglądarce wyłączone.

PRZEPIS DLA POCZĄTKUJĄCYCH


Pobieranie i ustawianie danych w pamięci sesji
W tym przepisie strona HTML użyje metod getItem i setItem obiektu sessionStorage do przechowywania
i pobierania liczby wyświetleń strony w sesji. Ta liczba jest zwiększana przy każdym odświeżeniu strony
i wyświetlana odwiedzającemu. Wykonaj następujące kroki, aby utworzyć stronę z listingu 11.1:
1. Utwórz pustą stronę HTML z elementem div o identyfikatorze divVisits.
2. Dodaj funkcję init w parze znaczników script, z kodem jak na listingu 11.1.
3. Dodaj procedurę obsługi zdarzenia window.addEventListener, aby po załadowaniu strony
uruchomić funkcję init.
Pobieranie i ustawianie danych w pamięci sesji 251

Listing 11.1. Wyświetlanie liczby wizyt na stronie przy użyciu sesyjnego przechowywania danych

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>11.1. Wizyty na stronie z sesyjnym przechowywaniem danych</title>
<script>
function init() {

// odwołanie do elementu div przeznaczonego do wyświetlania


var divVisits = document.getElementById('divVisits');

// sprawdź, czy przeglądarka obsługuje sessionStorage


if (window.sessionStorage) {

var visits; // liczba odwiedzin tej strony

// sprawdź, czy zmienna istnieje, używając notacji z kropką


if (sessionStorage.visits) {

// pobierz klucz i przekształć na int


visits = parseInt(sessionStorage.getItem('visits'));

// zwiększ visits
visits++;

} else {
// wartość domyślna dla pierwszej wizyty
visits = 1;
}

// aktualizuj zmienną visits


sessionStorage.setItem('visits',visits);

// wyświetl liczbę odwiedzin w sesji


divVisits.innerHTML = 'Liczba odwiedzin strony w sesji: ' + visits;

} else {
// sessionStorage nie jest dostępne
divVisits.innerHTML = 'sessionStorage nie jest dostępne';
}
}

// po załadowaniu uruchom funkcję init


window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id="divVisits"></div>
</body>
</html>

Kiedy załadujesz stronę utworzoną na listingu 11.1 w swojej przeglądarce, zostanie uruchomiona funkcja
init. Funkcja ta sprawdzi najpierw za pomocą window.sessionStorage, czy obiekt sessionStorage jest
dostępny w oknie. Jeśli obiekt sessionStorage nie jest dostępny, funkcja wyświetli komunikat w Twoim
elemencie div na stronie.
252 Rozdział 11. Przechowywanie danych po stronie klienta

Jeżeli obiekt sessionStorage jest dostępny, kod w JavaScripcie sprawdza następnie, czy klucz visits
istnieje, używając w tym celu notacji z kropką: sessionStorage.visits. Jeśli klucz visits istnieje, zostanie
zwrócona odpowiednia wartość, a sprawdzenie wypadnie pomyślnie. W przeciwnym razie jest to pierwsze
sprawdzenie w danej sesji i rozpoczynasz liczenie visits od wartości 1. Jeśli klucz jest przechowywany
w sesji, w celu zaprezentowania metody służącej do pobierania wartości możesz pobrać wartość dla klucza
metodą getItem. Kiedy wartość zostanie pobrana, za pomocą metody parseInt przekształć visits z wartości
tekstowej na liczbę całkowitą. Tę konwersję należy wykonać, ponieważ w pamięci przeglądarki wszystkie
wartości są przechowywane jako zwykłe ciągi znaków. Jeśli Twój skrypt zapisuje, a następnie pobiera inne
typy danych, będziesz musiał przekształcić te ciągi na ich właściwe typy.
Niezależnie od tego, czy pobrałeś liczbę odwiedzin i zwiększyłeś ją o 1, czy też ustawiłeś początkową
wartość na 1, skrypt aktualizuje następnie za pomocą metody setItem klucz visits w pamięci sesji. Jeśli
klucz nie znajdował się wcześniej w pamięci, metoda setItem doda parę klucz-wartość, w przeciwnym
wypadku wartość zostanie zastąpiona nową liczbą odwiedzin.

Narzędzia dla programistów Chrome


służące do oglądania zawartości pamięci
Po załadowaniu utworzonej na listingu 11.1 strony w Twojej przeglądarce możesz się zastanawiać,
jak sprawdzić, co dzieje się w czasie wykonywania skryptu w lokalnej pamięci sesji. Przeglądarka Google
Chrome ma wbudowany zestaw narzędzi (które możesz już znać) — są to Narzędzia dla programistów.
Jeśli otworzysz je w oknie swojej przeglądarki i odświeżysz stronę, będziesz mógł zobaczyć pary klucz-wartość
pamięci sesji w zakładce Resources (zasoby), jak pokazano na rysunku 11.1.

Rysunek 11.1. Podgląd par klucz-wartość pamięci sesji w narzędziach dla programistów przeglądarki Chrome

Na rysunku 11.1 domena, która zapisała klucze, jest pokazana po lewej stronie, pod zakładką Session
Storage (pamięć sesji), a po prawej stronie znajdują się pary klucz-wartość, które są aktualnie przechowywane.
W tym przypadku bieżąca wartość dla klucza visits to 2, a JavaScript pokazał to użytkownikowi. Narzędzia
dla programistów przeglądarki Chrome są przydatne przy pracy z pamięcią sesji i pamięcią lokalną, ponieważ
pozwalają nie tylko potwierdzić prawidłowe działanie skryptu, ale również dodawać, aktualizować i usuwać
Nadawanie stylów z pamięci sesji 253

pary klucz-wartość za pomocą interfejsu użytkownika. W obecnej wersji narzędzi dla programistów po zmianie
pary klucz-wartość należy odświeżyć panel widoku key/value (klucz/wartość) pamięci za pomocą położonego
u dołu przycisku Refresh (odśwież).

Wskazówka
Każdy obiekt, który ma wbudowaną metodę toString, może być zapisany w polu wartości pary
klucz-wartość pamięci sesji i pamięci lokalnej. Bardziej złożone obiekty o strukturze JSON mogą
być zapisane za pomocą metod JSON.stringify(twójObiekt) oraz JSON.parse(zwróconyCiąg),
przekształcających obiekt JSON na ciąg znaków, a pobrany ciąg znaków z powrotem na obiekt.

W tym przepisie w bardzo prosty sposób ustawialiśmy i pobieraliśmy pary klucz-wartość w pamięci sesji.
W kolejnym zajmiemy się obsługą błędów i dostępnością pamięci sesji pomiędzy stronami.

PRZEPIS DLA POCZĄTKUJĄCYCH


Nadawanie stylów z pamięci sesji
W tym przepisie udostępnisz odwiedzającemu Twoją stronę możliwość wyboru koloru motywu tła stron.
Wybór odwiedzającego zostanie zapisany w pamięci sesji i pobrany na kolejnej stronie w celu zarządzania
kolorem tła. Możliwe jest przywrócenie domyślnego motywu, co pozwoli na usunięcie zapisanej pary
klucz-wartość tła z pamięci sesji. Oto metoda removeItem, którą wykorzystamy w tym przepisie:
removeItem(klucz)

Klucz jest tutaj łańcuchem znakowym klucza do usunięcia.


Jak w przypadku każdego mechanizmu przechowywania danych, rozmiar zapisywanych w sesji i lokalnie
danych jest — w celu ochrony przeglądarki i komputera klienta — ograniczony. Każda przeglądarka ustawia
swój własny limit rozmiaru, ale w przeciwieństwie do ciasteczek zajmujących co najwyżej 4 kB, pamięć
przeznaczona na przechowywanie w sesji i lokalnie danych jest znacznie większa. Na przykład Internet
Explorer 9 miał w czasie pisania tej książki ograniczenie do 10 MB. Jeśli Twój skrypt spróbuje ustawić albo
zaktualizować wartość klucza i natknie się na taki limit, zostanie wygenerowany błąd. Aby zapobiec nagłemu
zakończeniu działania skryptu z tego powodu, powinieneś opakować wywołania metody setItem blokiem
try-catch. W tym przepisie pokażemy Ci, jak przechwycić taki błąd, jeśli się on zdarzy. Poniższe kroki
i listing 11.2 pozwolą Ci utworzyć pierwszą stronę przepisu:
1. Stwórz stronę z listingu 11.2 ze znacznikami style, script i body.
2. Dodaj elementy div z identyfikatorami themeContent i themeSettings.
3. Dodaj do swego skryptu funkcje initTheme, setTheme, resetTheme i applyTheme.
4. Dodaj polecenie window.addEventListener, aby uruchomić funkcję initTheme.

Listing 11.2. Ustawianie motywu w pamięci sesji

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>11.2. Wybór motywu - strona 1</title>
<style>
254 Rozdział 11. Przechowywanie danych po stronie klienta

#themeContent {
background-color:#FFF;
border-style:solid;
border-width:2px;
}
#themeSettings {
margin:10px;
}
</style>
<script>

var themeDiv; // element div do wyświetlenia rezultatu

// funkcja inicjalizująca motyw


function initTheme() {

// ustaw odwołanie do elementu div, służącego do wyświetlenia rezultatu


themeDiv = document.getElementById('theme');

// sprawdź, czy przeglądarka obsługuje sessionStorage


if (window.sessionStorage) {

// ustaw procedurę obsługi przycisku


var btnResetTheme = document.getElementById('resetTheme');
btnResetTheme.addEventListener('click',resetTheme,false);

// ustaw procedurę obsługi zmiany listy wyboru


var selThemeColor = document.getElementById('themeColor');
selThemeColor.addEventListener('change',setTheme,false);

// sprawdź, czy wcześniej ustawiliśmy kolor motywu


if (sessionStorage.themeColor) {

// ustaw początkowy kolor motywu


var themeColor = sessionStorage.getItem('themeColor');
document.getElementById(themeColor).selected = true;

applyTheme(themeColor);
}
} else {
themeDiv.innerHTML = 'sessionStorage nie jest obsługiwane.';
}
}

// ustaw wybrany motyw


function setTheme() {

// pobierz wybrany kolor motywu


var themeColor = document.getElementById('themeColor').value;

// użyj bloku try catch i ustaw kolor motywu


try {
sessionStorage.setItem('themeColor',themeColor);
applyTheme(themeColor);
}
catch(err){
// kod błędu 22 QUOTA_EXCEEDED_ERR mówi, że zabrakło miejsca
if(err.code == QUOTA_EXCEEDED_ERR){
themeDiv.innerHTML = 'Zabrakło miejsca w sessionStorage.';
Nadawanie stylów z pamięci sesji 255

// jeśli chcesz, obsłuż tutaj błąd w wybrany sposób


}
}
}

// funkcja czyszcząca kolor motywu


function resetTheme() {
// usuń pozycję z pamięci sesji
sessionStorage.removeItem('themeColor');

// wyczyść wyświetlanie
document.getElementById('default').selected = true;
document.body.style.backgroundColor = '';
themeDiv.innerHTML = 'Motyw wyczyszczony.';
}

// zastosuj motyw do strony


function applyTheme(themeColor) {
document.body.style.backgroundColor = themeColor;
themeDiv.innerHTML = 'Zastosowano motyw ' + themeColor + '. ';
}

// inicjalizuj okno
window.addEventListener('load',initTheme,false);

</script>
</head>
<body>
<div id="themeContent">
<div id="themeSettings">
<H1>Strona 1</H1>
Wybierz motyw:
<select id="themeColor">
<option id="default" value="">Wybierz kolor...</option>
<option id="blue" value="blue">niebieski</option>
<option id="red" value="red">czerwony</option>
<option id="yellow" value="yellow">żółty</option>
<option id="green" value="green">zielony</option>
</select>
<button id="resetTheme">Resetuj motyw</button><br><br>
<div id="theme"></div><br><br>
<a href="listing.11.3.html">Przejdź do strony 2</a>
</div>
</div>
</body>
</html>

Po ustawieniu motywu na stronie 1 (listing 11.2) możesz sprawdzić, czy został on zapisany, ładując inną
stronę, która odczyta zawartość pamięci i użyje odczytanej informacji do ustawienia właściwego motywu.
Wykonaj poniższe kroki i za pomocą listingu 11.3 utwórz drugą stronę, służącą do pokazania koloru motywu
wybranego wcześniej:
1. W oparciu o listing 11.3 utwórz nową stronę o podobnej strukturze.
2. Zmodyfikuj znaczniki a na obu stronach, aby odpowiadały nazwom Twoich stron i abyś dzięki
temu mógł się poruszać pomiędzy stronami.
256 Rozdział 11. Przechowywanie danych po stronie klienta

Listing 11.3. Pokazywanie motywu — strona 2

<!DOCTYPE html>
<html><head>
<meta charset="UTF-8" />
<title>11.3. Pokazywanie motywu - strona 2</title>
<style>
#themeContent {
background-color:#FFF;
border-style:solid;
border-width:2px;
}
#themeSettings {
margin:10px;
}
</style>
<script>

// inicjalizuj stronę
function init() {

// pobierz zapisany kolor motywu


var themeColor = sessionStorage.getItem('themeColor');
applyTheme(themeColor);
}

// zastosuj motyw do strony


function applyTheme(themeColor) {

document.body.style.backgroundColor = themeColor;

var themeDiv = document.getElementById('theme');


themeDiv.innerHTML = 'Motyw ' + themeColor + '.';

// inicjalizuj stronę
window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id="themeContent">
<div id="themeSettings">
<H1>Strona 2</H1>
<div id="theme"></div>
<br><br>
<a href="listing.11.2.html">Przejdź do strony 1</a>
</div>
</div>
</body>
</html>

Po załadowaniu strony 1 w Twojej przeglądarce pobiera ona z pamięci sesji aktualnie zapisany kolor
motywu. Kiedy strona jest uruchamiana po raz pierwszy, w pamięci nie ma motywu i zwracana jest pusta
wartość, więc motyw przyjmuje wartość domyślną przeglądarki. Przy kolejnych ładowaniach strony
pobierany jest kolor ustawiany jako kolor tła oraz jako wybrana wartość w kontrolce wyboru koloru.
Zapisywanie formularzy za pomocą lokalnego przechowywania danych 257

Uwaga
Aby wykonać przepisy korzystające z sesyjnego przechowywania danych, będziesz musiał załadować
strony na serwer, zamiast uruchamiać je w „trybie lokalnym”. Powodem jest to, że w trybie lokalnym
niektóre przeglądarki generowałyby błąd mówiący, że operacja nie jest obsługiwana. Na przykład
w Firefoxie zostałby wygenerowany błąd o kodzie 9: „Operation is not supported”.

Po załadowaniu strony odwiedzający może wybrać z listy rozwijanej opcji inny kolor, co spowoduje
zmianę koloru tła strony i zapisanie tego koloru w pamięci sesji pod kluczem themeColor. W skrypcie to
zapisujące ustawienia wywołanie setItem zostało opakowane w blok try-catch. Jeśli zostanie on aktywowany
z powodu ograniczeń rozmiaru, kod poinformuje odwiedzającego, że pamięć sesji jest pełna, ale nadal
będziesz mógł podjąć odpowiednie do potrzeb działania. Po ustawieniu koloru motywu użytkownik może
kliknąć łącze Przejdź do strony 2 i w ten sposób załadować drugą stronę. Jako że druga strona jest w tej samej
sesji co pierwsza, ona również będzie mieć dostęp do zapisanej na stronie 1 pary klucz-wartość z pamięci
sesji. Druga strona pobierze następnie odpowiednią wartość i ustawi tło na zapisany kolor.
Na listingu 11.2 dałeś odwiedzającemu również możliwość wyczyszczenia motywu. W tym przypadku
przepis usuwa parę klucz-wartość z pamięci sesji za pomocą metody removeItem. Przepis może również
aktualizować klucz, wywołując setItem z pustym ciągiem znaków.
Na początku tego rozdziału opisaliśmy, jak sesyjne przechowywanie danych pozwala Ci przechowywać
dane tylko dla konkretnej sesji. Jeśli załadujesz stronę utworzoną na listingu 11.2 do dwóch zakładek lub
okien w tej samej przeglądarce, a następnie wybierzesz różne kolory, każda zakładka będzie miała swój
własny kolor tła, ponieważ z każdą zakładką lub oknem związana jest osobna sesja. Aby wartości pozostały
dostępne w różnych oknach albo zakładkach przeglądarki, a nawet po zamknięciu i ponownym jej otwarciu,
musisz użyć obiektu lokalnego przechowywania danych. Przyjrzymy mu się w następnym przepisie.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Zapisywanie formularzy za pomocą lokalnego
przechowywania danych
Jak pokazano w poprzednich przepisach, sesyjne przechowywanie danych umożliwia zachowanie danych
przez okres trwania sesji odwiedzającego, co może być przydatne w przypadku magazynowania na krótki
czas. Czasami jednak będziesz chciał przechowywać dane w przeglądarce odwiedzającego w celu ich
wykorzystania po jego powrocie na stronę albo nawet pomiędzy aktualnie aktywnymi sesjami. W takim
przypadku z pomocą przychodzi lokalne przechowywanie danych. Korzysta ono z takiej samej listy par
klucz-wartość do przechowywania danych za pomocą metod getItem, setItem i removeItem. Najważniejszą
różnicą jest to, że dane, które zapisujesz w pamięci lokalnej, pozostają zapisane nawet po zamknięciu sesji.
Ten przepis obrazuje przykład zapamiętywania danych formularza, który odwiedzający wcześniej wypełnił.
Odwiedzający mógłby odwiedzić Twoją witrynę, otworzyć formularz, a następnie opuścić tę witrynę albo
zamknąć swoją przeglądarkę. Po powrocie na tę witrynę zastałby zapewne pusty formularz. W tym przepisie
w miarę wypełniania formularza JavaScript przechwytuje zmiany i zapisuje wartości pól formularza w pamięci
lokalnej. Jeśli strona zostanie następnie zamknięta i ponownie załadowana, sprawdza pamięć lokalną, żeby
dowiedzieć się, czy są tam przechowywane dane formularza, i automatycznie wypełnia go tymi danymi.
Podobna implementacja mogłaby nawet obsługiwać zbieranie danych z formularza wielostronicowego
i przechowywanie ich lokalnie aż do chwili wypełnienia całego formularza.
258 Rozdział 11. Przechowywanie danych po stronie klienta

W tym przepisie są wykorzystywane dwa nowe pojęcia związane z pamięcią sesji i lokalną: key oraz length.
Metoda key pozwala pobrać nazwę przechowywanego klucza przez dostarczenie jej wartości indeksu:
DOMString key(indeks)

Indeks jest tutaj indeksem pary klucz-wartość, dla której należy zwrócić wartość tekstową klucza.
Z kolei właściwość length pamięci sesji i pamięci lokalnej zwraca liczbę par klucz-wartość w pamięci.
Aby to uzyskać, użyj poniższego wywołania:
var numItems = localStorage.length;

W niniejszym przepisie użyjesz zarówno metody key, jak i właściwości length do przejścia w pętli przez
pary klucz-wartość localStorage. Wykonajmy następujące kroki, aby rozpocząć pracę z listingiem 11.4:
1. Utwórz pusty plik HTML i dodaj znaczniki HTML z listingu 11.4, zawierającego formularz,
którego będziesz używał, oraz jego pola. Dla uproszczenia użyliśmy w polach formularza atrybutu
onchange, ale mógłbyś go zastąpić procedurami nasłuchu zdarzeń dla każdego z pól.
2. Dodaj w sekcji head znaczniki script zawierające funkcję window.addEventListener,
która po załadowaniu strony uruchamia funkcję checkStorage.
3. Dodaj w skrypcie funkcję checkStorage, jak pokazano na listingu 11.4.
4. Dodaj w skrypcie funkcję changeField, która będzie obsługiwać zmiany pól formularza.
5. Załaduj plik w przeglądarce Chrome z narzędziami dla programistów otwartymi w obszarze
pamięci lokalnej i wpisz informacje w polach.

Listing 11.4. Zapisywanie zmian z formularza w localStorage

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>11.4. Przechowywanie danych formularza w pamięci lokalnej</title>
<script>

// funkcja odczytu pamięci i inicjalizacji formularza


function checkStorage() {

// sprawdź, czy pamięć lokalna jest dostępna


if (window.localStorage) {

var key, value, field;

// przejdź w pętli przez pamięć lokalną


for (var i = 0; i < localStorage.length; i++) {

// pobierz klucz
key = localStorage.key(i);

// ustaw pole na podstawie klucza


field = document.getElementById(key);

// sprawdź istnienie pola i przypisz wartość


if (field) {
// pobierz wartość
Zapisywanie formularzy za pomocą lokalnego przechowywania danych 259

value = unescape(localStorage.getItem(key));

// ustaw wartość pola


field.value = value;
}
} // zakończ pętlę for
} // zakończ sprawdzanie pamięci lokalnej
} // zakończ funkcję

// ustaw w localStorage wartość zmienionego pola


function changeField(formField) {

// sprawdź, czy pamięć lokalna jest dostępna


if (window.localStorage) {

var key, value;

// ustaw key na identyfikator pola formularza


key = formField.id;
// ustaw value na wartość pola formularza
value = escape(formField.value);

// spróbuj ustawić element w pamięci lokalnej


try {
localStorage.setItem(key, value);
}
catch (err) {
if (err.code == QUOTA_EXCEEDED_ERR) {
alert('Za mało miejsca w localStorage.');
}
}

} else {
alert('localStorage nie jest obsługiwane.');
}
}

// inicjalizuj formularz danymi z pamięci


window.addEventListener('load',checkStorage,false);

</script>
</head>
<body>
<h1>Mój formularz</h1>
<form id='myForm'>
<table>
<tr>
<td>Imię:</td>
<td><input type="text" id="firstName" onchange="changeField(this);" /></td>
</tr>
<tr>
<td>Nazwisko:</td>
<td><input type="text" id="lastName" onchange="changeField(this);" /></td>
</tr>
<tr>
<td>E-mail:</td>
<td><input type="email" id="email" onchange="changeField(this);" /></td>
</tr>
<tr>
260 Rozdział 11. Przechowywanie danych po stronie klienta

<td>Telefon:</td>
<td><input type="tel" id="phone" onchange="changeField(this);" /></td>
</tr>
</table>
</form>
</body>
</html>

Po wpisaniu w polu firstName imienia i przejściu za pomocą klawisza Tab do następnego pola przeglądarka
uruchomi zdarzenie onchange dla pola firstName i w konsekwencji wykona funkcję changeField z polem
firstName formularza przekazanym jako parametr. Funkcja changeField zapisze wtedy dane pola w parze
klucz-wartość w localStorage. Funkcja używa w parze klucz-wartość identyfikatora pola formularza jako
klucza, a wartości pola jako wartości. Jeśli zatem masz otwarte narzędzia dla programistów w Chrome,
powinieneś zobaczyć klucz firstName z wartością tekstu, który wpisałeś.
Aby poznać rzeczywiste możliwości localStorage, zamknij kartę lub okno przeglądarki, a następnie otwórz
je ponownie na tej samej stronie formularza. Po ponownym otwarciu i załadowaniu strony przeglądarka
wykona funkcję checkStorage. Funkcja ta ma za zadanie sprawdzić, czy zapisałeś wcześniej w pamięci
lokalnej jakieś informacje z formularza, i pobrać te informacje. Funkcja pobiera każdą parę klucz-wartość
poprzez przejście w pętli przez listę localStorage, sprawdzenie, czy istnieje identyfikator pola formularza,
który odpowiada nazwie klucza, oraz, jeśli taki istnieje, ustawienie wartości pola na pobraną wartość.
Dla zobrazowania możliwości localStorage korzystamy w tym przepisie z prostych pól tekstowych,
moglibyśmy jednak wykorzystać również inne rodzaje pól wejściowych formularza. Aby obsługiwać
różne działania dla różnych rodzajów pól, takich jak lista wyboru lub przycisk opcji, funkcje changeField
i checkStorage należałoby zaktualizować, ale wartość mogłaby być przechowywana w pamięci lokalnej,
tak jak zwykłe tekstowe dane wejściowe.
Kiedy strona przestaje korzystać z przechowywanych informacji formularza, a formularz zostaje
ostatecznie przesłany do serwera, powinieneś usunąć zawartość pól. Obok metody removeItem, którą
poznałeś wcześniej, API przechowywania danych udostępnia metodę służącą do czyszczenia całej pamięci
sesji lub pamięci lokalnej — clear. Metoda clear nie przyjmuje parametrów ani nie zwraca żadnego wyniku
— „czyści” z listy wszystkie pary klucz-wartość dla domeny strony. Oto przykład jej wywołania:
localStorage.clear();

Wykonanie tego wiersza usunie z lokalnej pamięci wszystkie pary klucz-wartość, bez względu na to,
która strona je dodała i kiedy zostały dodane. Ponieważ jest to metoda dość radykalna, powinieneś być
absolutnie pewny, że chcesz wyczyścić zawartość pamięci sesji albo pamięci lokalnej.
Z dotychczasowych przepisów dowiedziałeś się o podstawach dodawania, aktualizowania i usuwania
danych zapisanych dla witryny na komputerze klienta. Zakłada to, że w danym momencie odwiedzający
używa do interakcji z witryną jednej strony. Jeśli przechowywana informacja została zmieniona z innej
strony w przeglądarce, nie miałeś żadnego sposobu, aby automatycznie uwzględnić tę zmianę na wcześniej
otwartej stronie. API przechowywania danych po stronie klienta udostępnia zdarzenia aktualizacji pamięci
sesji i pamięci lokalnej, które możesz przechwycić i uwzględnić na otwartej stronie. Kolejny przepis łączy
opisane wcześniej metody ze zdarzeniami aktualizacji pamięci w celu odświeżenia zawartości Twojej strony.
Przechwytywanie zdarzeń w pamięci lokalnej 261

PRZEPIS DLA ZAAWANSOWANYCH


Przechwytywanie zdarzeń w pamięci lokalnej
W tym przepisie użyjesz metod i właściwości pamięci lokalnej do zaimplementowania paska bocznego, który
pozwoli odwiedzającemu robić notatki podczas przeglądania witryny. Notatki są przechowywane w pamięci
lokalnej przeglądarki odwiedzającego, a ich kolejność zostaje zachowana. Odwiedzający może aktualizować
notatkę, dodać nową albo usunąć jedną lub wszystkie swoje notatki. Dodatkowo, jeśli odwiedzający przegląda
witrynę na wielu kartach albo w wielu instancjach przeglądarki, notatki będą automatycznie aktualizowane
na wszystkich stronach, kiedy na jednej z nich zostanie dokonana jakakolwiek zmiana.
Aby w oparciu o zmianę na liście notatek odwiedzającego na karcie lub w oknie przeglądarki
automatycznie aktualizować listę notatek na innej karcie lub w innym oknie, będziesz musiał użyć
mechanizmu zdarzeń pamięci. Podobnie jak inne znane Ci zdarzenia, zdarzenie pamięci jest wywoływane,
kiedy modyfikowana jest pamięć sesji albo pamięć lokalna. Zdarzenie może wystąpić, kiedy dodawana,
aktualizowana albo usuwana jest para klucz-wartość. Procedura obsługi na stronie może nasłuchiwać
zdarzeń i wykonywać działania w oparciu o ich przechwycenie. Obiekt storageEvent zawiera atrybuty,
które pomogą określić działania, jakie należy podjąć (tabela 11.3). W tym przepisie przechwycisz zdarzenie,
sprawdzisz, czy pochodzi ono z pamięci lokalnej, powiadomisz odwiedzającego o zmianie i, wreszcie,
odświeżysz listę notatek, tak aby widoczna była aktualna lista.

Tabela 11.3. Atrybuty storageEvent

Atrybut Rodzaj Cel


key DOMString klucz, na którym wystąpiła zmiana
oldValue DOMString stara wartość
newValue DOMString nowa wartość
url DOMString adres URL strony, która dokonała zmiany
storageArea Storage obszar pamięci, w którym wystąpiła zmiana

Zacznijmy od następujących kroków:


1. Utwórz pustą stronę HTML ze znacznikami body i style, jak pokazano na listingu 11.5.
2. Dodaj znacznik script i deklarację zmiennej keyCode. Ta zmienna przechowuje klucz,
którym poprzedzisz każdą zapisywaną notatkę.
3. Dodaj wiersz window.addEventListener i funkcje initNoteBoard oraz updateNoteBoard.
4. Dodaj funkcje notatek: addNote, changeNote, updateNote i removeNote. Funkcja changeNote ładuje
notatkę do formularza aktualizacji, natomiast updateNote modyfikuje notatkę w pamięci lokalnej.
5. Dodaj funkcję clearAllNotes, służącą do umożliwienia odwiedzającemu wyczyszczenia planszy
z notatkami.
6. Dodaj funkcję obsługi zdarzeń onStorageEvent, która przechwyci wszelkie zdarzenia zapisu.
262 Rozdział 11. Przechowywanie danych po stronie klienta

Listing 11.5. Przechowywanie notatek w pamięci lokalnej

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>11.5. Przechowywanie notatek w pamięci lokalnej</title>
<style>
* {margin: 0; padding: 0;}
body {padding: 20px;}
h1 {font-size: 120%; margin: 0 0 .5em;}
section {width: 300px;}
#noteBoard, textarea {
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
-moz-box-shadow: 0px 0px 4px rgba(0,0,0,.4);
-webkit-box-shadow: 0px 0px 4px rgba(0,0,0,.4);
box-shadow: 0px 0px 4px rgba(0,0,0,.4);}
#noteBoard {
background: #FCFABA;
float: right;
padding: 10px 20px;}
#noteBoard div {
border-bottom: 1px dashed #CCC;
margin: 0 0 5px;
padding: 5px 0;
width: 100%;}
#noteBoard div.buttons {border: none;}
#addNote, #updateNote {float: left;}
#addNote {
border-right: 1px dashed #ccc;
margin: 0 50px 0 0;
padding: 0 50px 0 0;}
#updateNote { display:none;}
textarea {
border: none;
clear: both;
height: 150px;
margin: 0 0 10px;
padding: 10px;
width: 280px;}
input[type="text"] {margin: 0 0 10px; padding: 4px; }
button {padding: 5px;}
</style>

<script>

// prefiks notatek dla wpisów w pamięci


var keyCode = 'notatka';

// inicjalizuj planszę z notatkami


function initNoteBoard() {

// ustaw procedurę nasłuchu zmian w pamięci


window.addEventListener('storage', onStorageEvent, false);

// ustaw procedury nasłuchu dla nowych i aktualizowanych notatek


var btnAddNote = document.getElementById('btnAddNote');
var btnUpdateNote = document.getElementById('btnUpdateNote');
btnAddNote.addEventListener('click',addNote,false);
Przechwytywanie zdarzeń w pamięci lokalnej 263

btnUpdateNote.addEventListener('click',updateNote,false);

// aktualizuj wyświetlone notatki


updateNoteBoard();
}

// procedura obsługi zdarzeń pamięci


function onStorageEvent(eventObj) {
if (eventObj.storageArea == localStorage) {
// powiadom odwiedzającego o zmianie
alert(eventObj.key + ' zmieniona z "' +
eventObj.oldValue +
'" na "' +
eventObj.newValue + '".');

// aktualizuj wyświetlone notatki


updateNoteBoard();
}
}

// funkcja dodająca notatkę


function addNote() {

// pobierz liczbę notatek


var numNotes = parseInt(localStorage.getItem('numNotes'));
if (isNaN(numNotes)) {
numNotes = 0;
}

// ustaw klucz i wartość


var noteKey = keyCode+numNotes;
var noteValue = document.getElementById('note').value;

// zapisz notatkę
localStorage.setItem(noteKey, noteValue);

// aktualizuj liczbę notatek


numNotes++;
localStorage.setItem('numNotes', numNotes);

// aktualizuj planszę z notatkami


updateNoteBoard();

// wyczyść wpis notatki


document.getElementById('note').value = '';
}

// funkcja ładująca notatkę, która ma być aktualizowana


function changeNote(noteKey) {
// ustaw klucz i wartość w formularzu aktualizacji
document.getElementById('oldKey' ).value = noteKey;
document.getElementById('oldNote').value = localStorage.getItem(noteKey);

// pokaż obszar aktualizacji notatek


document.getElementById('updateNote').style.display = 'block';
}

// funkcja aktualizująca notatkę


function updateNote() {

// pobierz nowe wartości notatki


264 Rozdział 11. Przechowywanie danych po stronie klienta

var key = document.getElementById('oldKey').value;


var note = document.getElementById('oldNote').value;

// aktualizuj parę klucz-wartość


localStorage.setItem(key, note);

// wyczyść obszar aktualizacji


document.getElementById('updateNote').style.display = 'none';
document.getElementById('oldKey').value = '';
document.getElementById('oldNote').value = '';

// aktualizuj wyświetlone notatki


updateNoteBoard();
}

// funkcja usuwająca notatkę


function removeNote(noteKey) {

// pobierz liczbę notatek


var numNotes = parseInt(localStorage.getItem('numNotes'));

// wydobądź indeks klucza notatek z klucza notatek


keyIdx = parseInt(noteKey.substring(keyCode.length,noteKey.length));

// przejdź w pętli przez notatki i przesuń każdą z nich w dół listy


for (var i = keyIdx; i < numNotes; i++) {
localStorage.setItem(keyCode+i,localStorage.getItem(keyCode+(i+1)));
}

// aktualizuj liczbę notatek


numNotes--;
localStorage.setItem('numNotes',numNotes);

// usuń ostatnią notatkę, która jest teraz zduplikowana


localStorage.removeItem(keyCode + numNotes);

// aktualizuj wyświetlone notatki


updateNoteBoard();
}

// funkcja usuwająca wszystkie notatki


function clearAllNotes() {

// pobierz liczbę notatek


var numNotes = parseInt(localStorage.getItem('numNotes'));
if (isNaN(numNotes)) {
numNotes = 0;
}

// przejdź w pętli przez pary klucz-wartość notatek i usuń je


for (var i = 0; i < numNotes; i++) {
localStorage.removeItem(keyCode+i);
}

// aktualizuj liczbę notatek na 0


localStorage.setItem('numNotes','0');

// aktualizuj wyświetlone notatki


updateNoteBoard();
}
Przechwytywanie zdarzeń w pamięci lokalnej 265

// wyświetl notatki
function updateNoteBoard() {
// ustaw obszar wyświetlania
var noteBoard = document.getElementById('noteBoard');

// pobierz liczbę notatek


var numNotes = parseInt(localStorage.getItem('numNotes'));
// ustaw domyślną liczbę na 0, jeśli nie ma notatek
if (isNaN(numNotes)) {
numNotes = 0;
}

var notes = '<div>Moje notatki:</div>';


var key = '';
var value = '';

// przejdź w pętli przez notatki


for (var i = 0; i < numNotes; i++) {

// utwórz klucz z prefiksem


key = keyCode + i;

// pobierz klucz
value = localStorage.getItem(key);

// zbuduj kod wyświetlający dla tej notatki


notes += '<div><p>'+value+'</p><div class="buttons">'+
'<button onclick="changeNote(\''+key+'\');">Zmień</button>'+
'<button onclick="removeNote(\''+key+'\');">Usuń</button>'+
'</div>'+
'</div>';
}

// zakończ kod wyświetlający


notes += '<div style="float:right;"><button id="clearAllNotes">Usuń wszystkie notatki</button></div>';

// ustaw listę do wyświetlenia


noteBoard.innerHTML = notes;

// ustaw procedurę nasłuchu do czyszczenia wszystkich notatek


var btnClearAllNotes = document.getElementById('clearAllNotes');
btnClearAllNotes.addEventListener('click',clearAllNotes,false);

// inicjalizuj planszę z notatkami


window.addEventListener('load',initNoteBoard,false);

</script>
</head>
<body>
<h1>Plansza z notatkami</h1>
<section id="noteBoard"></section>
<section id="addNote">
<h1>Tutaj dodaj nową notatkę:</h1>
<textarea name="note" id="note"></textarea>
<button id="btnAddNote">Dodaj notatkę</button>
</section>
<section id="updateNote">
<h1>Aktualizuj notatkę</h1>
<input type="text" name="oldKey" id="oldKey" disabled />
266 Rozdział 11. Przechowywanie danych po stronie klienta

<textarea name="oldNote" id="oldNote"></textarea>


<button id="btnUpdateNote">Aktualizuj notatkę</button>
</section>
</body>
</html>

Po wczytaniu strony przeglądarka uruchamia funkcję initNoteBoard. Funkcja ta instaluje procedurę


nasłuchu do przechwytywania wszelkich zdarzeń pamięci. Te zdarzenia będą obsługiwane przez funkcję
onStorageEvent. Następnie initNoteBoard aktualizuje wyświetlane notatki, sprawdzając pamięć lokalną.
W funkcji updateNoteBoard skrypt będzie najpierw pobierał klucz numNotes, który jest licznikiem zapisanych
wcześniej notatek. Następnie, korzystając z numNotes, skrypt przejdzie w pętli przez każdą parę klucz-wartość
i pobierze ją, używając keyCode o postaci „notatka” z indeksem zaczynającym się od 0. A zatem aby pobrać
pierwszą notatkę, strona wywołuje getItem z kluczem notatka0. Klucze keyCode i numNotes są używane z dwóch
powodów. Po pierwsze, dzięki ich użyciu nie musisz przetwarzać w pętli żadnych innych par klucz-wartość,
które mogą być przechowywane w pamięci lokalnej dla danej witryny, a po drugie, pozwalają one zachować
kolejność notatek. Pamiętaj, że w przypadku pamięci sesji i pamięci lokalnej nie ma określonego porządku
par klucz-wartość na liście. Przeglądarka wstawi je przy użyciu setItem w przypadkowej kolejności (patrz
rysunek 11.2).

Rysunek 11.2. Przykład pokazujący wiele par klucz-wartość w pamięci lokalnej

Prawdziwa siła tego przepisu leży w obsłudze zdarzeń. Jeśli załadujesz stronę w dwóch zakładkach lub
oknach przeglądarki i dodasz nową notatkę w jednym z okien, zostanie uruchomiona procedura obsługi
zdarzeń. W tym przypadku przeglądarka wywoła funkcję onStorageEvent, która wykona dwa działania.
Po pierwsze, powiadomi odwiedzającego, że wystąpiła zmiana, i wyświetli szczątkową informację na jej
temat. Po drugie, procedura obsługi zdarzeń wywoła funkcję aktualizacji wyświetlania w celu pokazania
uaktualnionej listy notatek. Jeśli poeksperymentujesz z dodawaniem, aktualizowaniem i usuwaniem notatek,
zrozumiesz działanie tej procedury.
Web SQL Database API 267

Jeśli usuniesz notatkę, procedura obsługi zdarzeń pokaże Ci cały szereg zmian zachodzących w pamięci
lokalnej. Wynika to z utrzymywania kolejności notatek. Aby zachować kolejność na liście, skrypt wykorzystuje
algorytm, w którym wpisy znajdujące się po tym przeznaczonym do usunięcia są przesuwane w dół listy,
a ostatni wpis jest usuwany. Pamiętaj, że metoda clear usuwa nie tylko pary klucz-wartość, które chciałbyś
usunąć, ale wszystkie pary klucz-wartość z pamięci.

Uwaga
Zdarzenia pamięci nie są wyzwalane na tej samej stronie, która dokonała modyfikacji pamięci sesji
albo pamięci lokalnej. Są one powiadomieniami propagowanymi do innych okien i zakładek przeglądarki.
Aby przechwycić zmianę na stronie, która jej dokonała, musisz wywołać odpowiednie działania
bezpośrednio po dokonaniu tej zmiany.

W tym przepisie użyłeś konkretnego klucza, numNotes, do przechowywania liczby wprowadzonych


notatek. Aby zachować kolejność notatek, użyłeś prefiksu klucza ze zwiększającą się liczbą, na przykład
notatka0, notatka1 i notatka2. A jeśli skrypt miałby posortować listę w oparciu o inne kryteria albo
skatalogować Twoje notatki według kategorii? Mógłbyś opracować własną metodę przy użyciu JavaScriptu
i obiektów, ale dzięki HTML5 i jego definicji Web SQL Database API to zadanie staje się całkiem proste,
jak zobaczysz w ostatnim przepisie tego rozdziału.

Web SQL Database API


Jak dowiedziałeś się z poprzednich przepisów niniejszego rozdziału, Web Storage API pozwala przechowywać
w przeglądarce klienta proste dane. Jeśli chcesz przechowywać bardziej złożone obiekty, właściwości
i relacje, będziesz musiał wykorzystać Web SQL Database API albo Indexed Database API. Podobnie jak
inne API, nie są one częścią podstawowej specyfikacji HTML5, lecz zestawem dodatkowych funkcjonalności
implementowanych przez przeglądarki. Ogólnie rzecz biorąc, Web SQL Database API jest obecnie obsługiwane
przez przeglądarki, natomiast Indexed Database API jest nadal definiowane i ma ograniczone wsparcie.
Zapewne z czasem Indexed Database będzie bardziej rozpowszechnione, ale dla celów demonstracyjnych
w tym rozdziale skupimy się na Web SQL Database API.
Jeśli miałeś okazję pracować nad stronami po stronie serwera i dostępem do bazy danych,
najprawdopodobniej używałeś podobnej, opartej o SQL bazy danych. Jeśli nie, tutaj masz okazję poznania
potężnego systemu zarządzania przechowywaniem danych w samej przeglądarce. Tabela 11.4 przedstawia
wsparcie przeglądarek dla Web SQL Database API.

Tabela 11.4. Dostępność Web SQL Database w przeglądarkach

Android 2.1+
Chrome 9.0+
Firefox -
Internet Explorer -
iOS Safari 3.2+
Opera 10.6+
Safari 3.2+
268 Rozdział 11. Przechowywanie danych po stronie klienta

Specyfikacja Web SQL Database API jest oparta na SQLite, więc jeśli używałeś już SQLite w środowisku
mobilnym, takim jak środowisko iOS, będzie Ci ona znana. A jeśli używałeś bazy danych po stronie serwera,
takiej jak MySQL, rozpoznawalne będą podstawowe polecenia oraz struktura. Jeśli nie masz doświadczenia
z SQL-em, możesz poszukać jakichś zasobów dotyczących poleceń SQL, ale podstawowe polecenia do tworzenia,
odczytu, modyfikacji i usuwania danych omówimy w następnym przepisie. Tak jak w przypadku pamięci
sesji i pamięci lokalnej, dla celów bezpieczeństwa sieciowa baza danych jest dostępna tylko dla stron z tej
samej domeny, dla której została utworzona. Jednak w przeciwieństwie do pamięci sesji i pamięci lokalnej,
sieciowa baza danych może przechowywać złożone dane z informacjami o powiązaniach między nimi.

Uwaga
Trwa debata na temat formatu bazy danych, który ma być używany w Web SQL Database API.
W specyfikacji Web Database organizacji W3C został wymieniony SQLite, wdrożony zresztą w kilku
przeglądarkach, jednak dopóki nie ma jednogłośnej akceptacji, grupa sieciowej bazy danych z W3C
zawiesiła pracę nad tą funkcjonalnością. Spowodowało to promocję specyfikacji Indexed Database,
która zyskuje na atrakcyjności.

Do interakcji z sieciową bazą danych API udostępnia trzy podstawowe asynchroniczne polecenia. Są one
asynchroniczne, aby renderowanie strony w przeglądarce nie było „blokowane” podczas oczekiwania na
wynik wywołania bazodanowego. W zależności od ilości żądanych w transakcji danych strona może czekać
nawet kilka sekund na zwrócenie zbioru danych. Trzy podstawowe polecenia pozwalają Ci otworzyć (lub
utworzyć) bazę danych, utworzyć transakcję i wykonać polecenia SQL w ramach transakcji:
„ openDatabase — otwiera istniejącą bazę danych lub tworzy na podstawie przekazanych
parametrów nową, jeśli baza danych nie istnieje.
„ transaction — grupuje polecenia dla bazy danych, dzięki czemu możesz w razie potrzeby
wycofać całą transakcję. Dostępna jest wersja tylko do odczytu — readTransaction.
„ executeSql — metoda uruchamiająca właściwe polecenie SQL na otwartej bazie danych.

Metoda openDatabase otwiera bazę danych, a jeśli baza danych nie istnieje, najpierw automatycznie
tworzy ją na podstawie zadanych parametrów:
WindowDatabase openDatabase(name, version, displayName, estimatedSize [, creationCallBack])

Parametry metody openDatabase są następujące:


„ name — nazwa bazy danych,
„ version — numer wersji, który przypisujesz bazie danych,
„ displayName — przyjazna dla użytkownika wyświetlana nazwa bazy danych,
„ estimatedSize — zakładany rozmiar bazy danych,
„ creationCallBack — (opcjonalny) funkcja wywoływana, gdy baza danych została utworzona
po raz pierwszy.

Metoda zwraca obiekt WindowDatabase, wykorzystywany następnie do wykonywania transakcji. Baza


danych jest automatycznie zamykana, gdy okno przeglądarki lub karta zostają zamknięte.
Web SQL Database API 269

Wskazówka
Numer wersji w metodzie openDatabase przydaje się do śledzenia wersji struktury i danych Twojej bazy
danych. Może to być użyteczne, jeśli używasz bazy danych w celu szybkiego, lokalnego wyszukiwania
danych. Kiedy wyszukane przez Ciebie dane ulegają zmianie, a użytkownik powraca na Twoją witrynę,
możesz sprawdzić wersję dzięki odpowiedniej wartości zapisanej w tabeli w głównej bazie danych.
Jeśli baza danych do wyszukiwania nie została zaktualizowana, możesz umieścić na serwerze nową.
Podczas otwierania możesz także użyć kodu błędu do potwierdzenia, że jest to zła wersja.

Aby zrealizować zapytanie w bazie danych, należy wykonać żądanie transakcji. Metoda transaction
opakowuje jedno lub większą liczbę poleceń executeSql, aby utworzyć pojedynczą transakcję, w ramach
której baza danych ma działać. Transakcja może być wycofana, jeśli którekolwiek z zapytań SQL skończy
się niepowodzeniem. Parametr callback transakcji zawiera funkcję dla metod executeSql:
transaction(callback [, errorCallback] [, successCallback])

Parametry metody transaction są następujące:


„ callback — funkcjonalność do wykonania w ramach transakcji,
„ errorCallback — (opcjonalny) funkcja do obsługi wszelkich pojawiających się błędów,
„ successCallback — (opcjonalny) funkcja sukcesu, wywoływana, jeśli wszystkie procesy w ramach
transakcji były udane.

Metoda executeSql przyjmuje jako pierwszy parametr Twoje polecenie zapytania SQL i może zawierać
tablicę opcjonalnych argumentów, funkcję wywołania zwrotnego w przypadku prawidłowego wykonania
zapytania i opcjonalną funkcję wywołania zwrotnego w przypadku błędu, jak pokazano poniżej:
executeSql(sqlStatement [, arguments] [, callback] [, errorCallback])

Parametry metody executeSql są następujące:


„ sqlStatement — polecenie SQL do wykonania,
„ arguments — (opcjonalny) tablica argumentów opcjonalnych,
„ callback — (opcjonalny) funkcjonalność do wykonania, jeśli wykonanie polecenia SQL
będzie poprawne,
„ errorCallback — (opcjonalny) funkcjonalność do wykonania, jeśli wykonanie polecenia SQL
zwróci błąd.

Parametr opcji procedury obsługi błędów dla metod API baz danych jest używany do przechwytywania
wszelkich błędów, które mogłyby wystąpić podczas pracy z bazą danych. Może to obejmować różne przypadki,
takie jak błąd w Twoim SQL-u lub problem z dostępem do bazy danych:
„ UNKNOWN_ERR (0) — wystąpił nieznany błąd, spoza wymienionych tutaj.
„ DATABASE_ERR (1) — w bazie danych wystąpił błąd, który nie jest objęty kategorią błędu.
„ VERSION_ERR (2) — wersja podana w poleceniu nie pasuje do wersji bazy danych.
„ TOO_LARGE_ERR (3) — zbiór danych uzyskanych w wyniku Twojego zapytania z bazy danych
był zbyt duży, aby mógł zostać zwrócony.
270 Rozdział 11. Przechowywanie danych po stronie klienta

„ QUOTA_ERR (4) — brak miejsca do przechowywania lub użytkownik odmówił przydzielenia


większej ilości miejsca dla Twojej bazy danych.
„ SYNTAX_ERR (5) — żądanie nie powiodło się z powodu błędu składni.
„ CONSTRAINT_ERR (6) — żądanie nie powiodło się, ponieważ naruszyło ograniczenie w strukturze
bazy danych.
„ TIMEOUT_ERR (7) — transakcja nie powiodła się, ponieważ nie mogła ustanowić blokady bazy
danych w odpowiednim czasie.

Często spotykanym błędem jest SYNTAX_ERR, który oznacza problem ze składnią Twojego polecenia SQL.
Komunikat będący częścią obiektu błędu przekazanego do Twojej procedury obsługi błędu będzie miał
decydujące znaczenie w przekazaniu Ci większej ilości szczegółów na temat danego kodu błędu.
W tym momencie mógłbyś zapytać, w jaki sposób wszystko to działa wspólnie, realizując przechowywanie
i wyszukiwanie informacji w bazie danych. Pokazuje to kolejny przepis, prezentujący łączne wykorzystanie
tych podstawowych metod.

PRZEPIS DLA ZAAWANSOWANYCH


Użycie sieciowej bazy danych
do stworzenia listy zakupów
Ten przepis wykorzysta interfejsy bazy sieciowej, które właśnie opisaliśmy, do utworzenia listy zakupów,
w całości przechowywanej w zasobach bazy danych przeglądarki. Odwiedzający będą mogli dodawać do
listy elementy — nazwę i ilość — oraz wybierać z rozwijanej listy dział sklepu spożywczego, w którym dany
element zostanie wyszukany. Po dodaniu elementu lista zakupów będzie aktualizowana i wyświetlana. Lista
będzie pogrupowana według działów, aby wyprawa na zakupy była łatwiejsza. Odwiedzający może usunąć
pozycje z listy i całkowicie wyczyścić listę. Jeśli odwiedzający opuści stronę, a następnie do niej powróci,
strona pokaże przechowywane produkty.
Tworzona w tym przepisie baza danych będzie miała dwie tabele: tabelę groceryitems, przechowującą
pozycje listy, oraz tabelę departments, która będzie tabelą referencyjną z predefiniowaną listą działów. Kiedy
strona zostanie po raz pierwszy załadowana, zostaną utworzone baza danych i tabele, a tabela departments
zostanie dodatkowo wypełniona informacjami o działach. Tabela groceryitems będzie miała cztery pola,
jak określono w tabeli 11.5.

Tabela 11.5. Struktura tabeli groceryitems

Pole Typ Opis


itemid INTEGER unikatowy główny klucz dla każdego rekordu w tabeli
quantity INTEGER liczba produktów z tego konkretnego rekordu do zakupienia
itemname TEXT nazwa produktu
deptid INTEGER identyfikator działu, który pochodzi z tabeli departments

Tabela departments będzie miała dwa pola, jak określono w tabeli 11.6.
Użycie sieciowej bazy danych do stworzenia listy zakupów 271

Tabela 11.6. Struktura tabeli departments

Pole Typ Opis


deptid INTEGER unikatowy główny klucz dla każdego działu w tabeli
deptname TEXT nazwa działu

Kiedy tabela departments zostanie utworzona, skrypt doda do niej domyślnie cztery działy.

Wskazówka
Podobnie jak w przypadku pamięci sesji i pamięci lokalnej, narzędzia dla programistów przeglądarki
Chrome pozwalają w prosty sposób przeglądać bazę danych SQLite w czasie wykonywania skryptu.
W oknie Narzędzia dla programistów w zakładce Resources (zasoby) możesz odnaleźć punkt Database
(baza danych). W przeciwieństwie do elementów pamięci sesji i pamięci lokalnej, tabel i danych bazy
danych nie da się edytować w narzędziach dla programistów, ale ta funkcjonalność zostanie być może
w przyszłości dodana.

Dla celów diagnostycznych na stronie umieszczono także przycisk Usuń bazę danych, pozwalający
na usunięcie bazy danych. Aktualna specyfikacja sieciowej bazy danych nie zawiera metody usuwania
bazy danych. Aby obejść ten problem i usunąć bazę danych, należy najpierw ją opróżnić, pozbywając się
utworzonych w niej tabel. W trakcie programowania przy użyciu Web SQL Database API użyteczne może
być ponowne tworzenie bazy danych, dzięki któremu możesz weryfikować odpowiedzialne za to zadanie
SQL-owe transakcje.
Aby utworzyć stronę z listą zakupów, wykonaj następujące kroki:
1. Utwórz podstawową strukturę strony ze znacznikami HTML, deklaracją zmiennej shoppingdb,
procedurą obsługi zdarzenia załadowania strony window.addEventListener oraz javascriptową
funkcją init.
2. Dodaj wywoływane z funkcji init funkcje javascriptowe: openShoppingDb i dbPresent.
3. Aby zakończyć inicjalizację bazy danych, dodaj funkcje initShoppingDb, onDbError i nullHandler.
Funkcja initShoppingDb tworzy tabele i wpisuje do nich działy. Funkcja onDbError jest ogólną
funkcją obsługującą błędy bazy danych, które mogą się pojawić, kiedy skrypt wykonuje transakcje,
natomiast funkcja nullHandler jest używana do przechwytywania zdarzeń udanych transakcji.
Jeśli załadujesz teraz stronę, powinieneś zobaczyć bazę danych utworzoną przez Twój skrypt
w narzędziach dla programistów Chrome.
4. Aby produkty spożywcze były prezentowane, gdy wyświetlana jest strona, dodaj funkcje initPage,
getDepartments i showDepartments. Technika „pobierz i pokaż” jest tu szczególnie przydatna,
ponieważ żądania transakcyjne są asynchroniczne i wymagają funkcji zwrotnej do obsługi
wyników. W tym przypadku funkcja getDepartments wykonuje żądanie pobrania działów z tabeli
departments, a funkcja showDepartments je wyświetla. Dzięki funkcji showDepartments utworzysz
opcje dla listy wyboru.
5. Aby zakończyć wyświetlanie w czasie inicjalizacji, powinieneś pokazać wszelkie produkty
spożywcze, które zostały wcześniej zapisane w bazie danych — w tym celu dodaj getGroceryItems
i showGroceryItems z listingu 11.6.
272 Rozdział 11. Przechowywanie danych po stronie klienta

6. Aby dodać produkt, wywoływana jest funkcja addGroceryItem, która pobiera dane wejściowe z pól
wejściowych i wstawia je do tabeli groceryitems. Jeśli operacja się powiedzie, strona wyświetli
nową listę produktów. Aby móc usunąć produkt, dodaj metodę deleteItem.
7. Aby dokończyć tworzenie strony, dodaj metodę resetGroceryList, która czyści listę zakupów,
oraz diagnostyczną funkcję removeDatabase, która usuwa tabele bazy danych.

Listing 11.6. Tworzenie listy zakupów przy użyciu bazy danych po stronie klienta

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>11.6. Bazodanowa lista zakupów</title>
<style>
section {
margin-bottom:20px;
}
</style>
<script>

var shoppingdb = null; // odwołanie do bazy danych

// funkcja rozpoczynająca inicjalizację strony


function init() {

// sprawdź, czy obsługa baz danych jest dostępna


if (window.openDatabase) {

// ustaw procedury obsługi kliknięcia przycisków


var btnAddGroceryItem = document.getElementById('addGroceryItem');
var btnResetGroceryList = document.getElementById('resetGroceryList');
var btnRemoveDatabase = document.getElementById('removeDatabase');
btnAddGroceryItem.addEventListener('click',addGroceryItem,false);
btnResetGroceryList.addEventListener('click',resetGroceryList,false);
btnRemoveDatabase.addEventListener('click',removeDatabase,false);

// otwórz bazę danych


openShoppingDb();

// sprawdź, czy istnieje odwołanie do bazy danych


if (shoppingdb) {

// sprawdź, czy baza danych jest zainicjalizowana


dbPresent();
}
} else {
alert('Bazy danych nie są obsługiwane w tej przeglądarce');
}
}

// funkcja otwierająca bazę danych


function openShoppingDb() {

// spróbuj otworzyć bazę danych


try {
var dbSize = 5000000; // rozmiar 5MB
shoppingdb = openDatabase('shoppingdb', '1.0', 'lista zakupów', dbSize);
} catch (err) {
Użycie sieciowej bazy danych do stworzenia listy zakupów 273

// wystąpił błąd podczas otwierania bazy danych


shoppingdb = null;
console.log('Błąd przy otwieraniu bazy danych: ' + err.code + ' - ' + err.message);
return;
}
}

// funkcja sprawdzająca, czy tabele bazy danych są obecne


function dbPresent() {
// rozpocznij transakcję
shoppingdb.readTransaction(function(tx) {
// wykonaj sql wyciągający pierwszy rekord
// jeśli się udało, to inicjalizuj stronę
// jeśli nie, inicjalizuj bazę danych
tx.executeSql('SELECT 1 FROM departments', [],
initPage, initShoppingDb);
});
}

// funkcja inicjalizująca stronę


function initPage() {
// pobierz działy
getDepartments();

// załaduj produkty spożywcze


getGroceryItems();
}

// inicjalizuj bazę danych zakupów


function initShoppingDb() {
// rozpocznij transakcję w bazie danych
shoppingdb.transaction(function(tx) {
// utwórz tabele w bazie danych
tx.executeSql('CREATE TABLE IF NOT EXISTS ' +
'groceryitems(itemid INTEGER NOT NULL PRIMARY KEY, quantity INTEGER, itemname TEXT,
´deptid INTEGER)',
[], nullHandler, onDbError);
tx.executeSql('CREATE TABLE IF NOT EXISTS ' +
'departments(deptid INTEGER NOT NULL PRIMARY KEY, deptname TEXT)',
[], nullHandler, onDbError);

// wypełnij tabelę departments


tx.executeSql('INSERT INTO departments(deptname) VALUES (?)',
['Świeże produkty'], nullHandler, onDbError);
tx.executeSql('INSERT INTO departments(deptname) VALUES (?)',
['Delikatesy'], nullHandler, onDbError);
tx.executeSql('INSERT INTO departments(deptname) VALUES (?)',
['Piekarnia'], nullHandler, onDbError);
tx.executeSql('INSERT INTO departments(deptname) VALUES (?)',
['Artykuły spożywcze'], initPage, onDbError);
});
}

// standardowa funkcja błędu w bazie danych


function onDbError(tx, err) {
alert('Wystąpił błąd w bazie danych: ' + err.code + '|' + err.message );
}

// pusta procedura obsługi w przypadku sukcesu


function nullHandler(tx, r) {
return;
274 Rozdział 11. Przechowywanie danych po stronie klienta

// funkcja pobierająca listę działów


function getDepartments() {
// rozpocznij transakcję w bazie danych
shoppingdb.readTransaction(function(tx) {
// wykonaj sql służący do pobrania działów
tx.executeSql('SELECT * FROM departments ORDER BY deptname ASC', [],
showDepartments, onDbError);
});
}

// funkcja pokazująca pobrane działy


function showDepartments(tx, rs) {
// pobierz odwołanie do obiektu wyboru działu
var selectObj = document.getElementById('department');

// przejdź w pętli przez zbiór rekordów działów i dodaj do obiektu wyboru


for (var i=0; i < rs.rows.length; i++) {
row = rs.rows.item(i);
selectObj.options[selectObj.options.length] =
new Option(row.deptname, row.deptid, false, false);
}
}

// funkcja do pobrania produktów spożywczych


function getGroceryItems() {
// rozpocznij transakcję w bazie danych
shoppingdb.readTransaction(function(tx) {
// pobierz listę produktów
tx.executeSql('SELECT * FROM groceryitems, departments WHERE groceryitems.deptid=departments.deptid
´ORDER BY deptname ASC',
[], showGroceryItems, onDbError);
});
}

// funkcja pokazująca listę produktów spożywczych


function showGroceryItems(tx, rs) {
var myShoppingList = document.getElementById('myShoppingList');
var tableRow = '<table>';
var row = null;

// ustaw nagłówki tabeli


tableRow += '<tr><td>Ilość</td><td>Produkt</td><td>Dział</td><td>Usuń</td></tr>';

// przejdź w pętli przez zwrócony zbiór rekordów


for (var i=0; i < rs.rows.length; i++) {
// pobierz wiersz
row = rs.rows.item(i);

// zbuduj wiersz tabeli


tableRow += '<tr><td>' + row.quantity + '</td>' +
'<td>' + row.itemname + '</td>' +
'<td>' + row.deptname + '</td>' +
'<td><button onclick="deleteItem(' +
row.itemid + ');">X</button></td></tr>';
}
tableRow += '</table>';

// ustaw informację w elemencie div


myShoppingList.innerHTML = tableRow;
Użycie sieciowej bazy danych do stworzenia listy zakupów 275

// dodaj produkt spożywczy do tabeli groceryitems w bazie danych


function addGroceryItem() {
// otwórz asynchroniczne wywołanie transakcji w bazie danych
shoppingdb.transaction(function(tx){
// pobierz dane produktu spożywczego
var itemname = document.getElementById('item').value;
var quantity = parseInt(document.getElementById('quantity').value);
var deptid = parseInt(document.getElementById('department').value);

// wykonaj insert executeSql


tx.executeSql('INSERT INTO groceryitems(quantity, itemname, deptid) VALUES (?,?,?)',
[quantity, itemname, deptid], getGroceryItems, onDbError);
});
}

// funkcja usuwająca produkt z listy


function deleteItem(id) {
// rozpocznij transakcję
shoppingdb.transaction(function(tx) {
// wykonaj sql usuwający produkt z bazy danych
tx.executeSql('DELETE FROM groceryitems WHERE itemid=?',
[id], getGroceryItems, onDbError);
});
}

// funkcja czyszcząca listę zakupów


function resetGroceryList() {
// rozpocznij transakcję
shoppingdb.transaction(function(tx) {
// wykonaj sql usuwający dane z tabeli produktów spożywczych
tx.executeSql('DELETE FROM groceryitems', [], getGroceryItems, onDbError);
});
}

// funkcja usuwająca bazę danych


function removeDatabase() {
// rozpocznij transakcję
shoppingdb.transaction(function(tx) {
// usuń tabele
tx.executeSql('DROP TABLE departments', [], nullHandler, onDbError);
tx.executeSql('DROP TABLE groceryitems', [], nullHandler, onDbError);
});
}

// wywołaj init po załadowaniu


window.addEventListener('load',init,false);

</script>
</head>
<body>
<section>
Ilość:<input type="number" id="quantity" />
Produkt:<input type="text" id="item" placeholder="mleko" />
Dział:<select id="department"></select>
<button id="addGroceryItem">Dodaj produkt</button>
</section>
<section id="myShoppingList"></section>
<section>
276 Rozdział 11. Przechowywanie danych po stronie klienta

<button id="resetGroceryList">Wyczyść listę zakupów</button>


<button id="removeDatabase">Usuń bazę danych</button>
</section>
</body>
</html>

Po załadowaniu strony w przeglądarce funkcja init weryfikuje dostępność Web SQL Database poprzez
sprawdzenie, czy dostępna jest metoda window.openDatabase. Jeśli tak, wywoływana jest metoda openDatabase
z parametrami bazy danych. W tym przypadku skrypt określa wielkość bazy na 5 MB, ponieważ przeglądarki
wydają się nie współpracować z użytkownikiem przy tworzeniu baz danych tej wielkości lub mniejszych. Jeśli
Twoja baza danych przekroczy ten rozmiar podczas tworzenia lub później, przeglądarka zapyta użytkownika,
czy może przeznaczyć na bazę więcej miejsca. Jeśli uruchamiasz kod po raz pierwszy, baza danych zostanie
utworzona, a jej uchwyt zwrócony; w przeciwnym wypadku baza danych zostanie otwarta.
Ponieważ przepis ten używa do załadowania listy wyboru działów wartości z tabeli departments,
w przypadku nowej instancji bazy danych musisz wypełnić tę tabelę swoimi danymi referencyjnymi. Aby
sprawdzić, czy należy utworzyć tabele, w metodzie dbPresent skrypt weryfikuje ich obecność, wykonując
proste zapytanie, i stara się wybrać pierwszy wiersz w tabeli działów. Jeśli tabela nie istnieje, w metodzie
initShoppingDb skrypt zarówno tworzy tabele, jak i wstawia wartości działów do tabeli departments. Gdy
baza danych jest załadowana, skrypt wczytuje działy na listę wyboru i wyświetla wszelkie zapisane produkty
spożywcze poprzez wywołanie metody getGroceryItems. Metoda getGroceryItems otwiera kolejną transakcję,
aby zwrócić wszystkie pozycje z tabeli groceryitems, oraz, w przypadku sukcesu, przekazuje wynikowy zbiór
wierszy metodzie showGroceryItems. Metoda show przejdzie następnie w pętli przez produkty spożywcze
i wyświetli je we fragmencie strony o identyfikatorze myShoppingList.

Uwaga
Metoda transaction służy do poleceń SQL, które wymagają uprawnień do zapisu w bazie danych.
Poprzez wymaganie prawa zapisu transakcja ustawia blokadę zapisu w bazie danych, co uniemożliwia
innym pisanie do bazy danych w tym czasie. Jeśli wykonujesz w bazie danych wyłącznie polecenia
odczytu, powinieneś użyć metody readTransaction. Metoda readTransaction jest wywoływana tak
samo jak metoda transaction.

Gdy strona jest zainicjowana, a baza danych i działy są wypełnione, odwiedzający może wprowadzić
produkt i ilość oraz wybrać dział dla nowej pozycji na swojej liście zakupów. addGroceryItem uwzględni
następnie te informacje, utworzy transakcję bazy danych i wykona SQL-owe polecenie wstawiania danych,
aby dodać element w wywołaniu funkcji. Jeżeli wstawienie danych nie powiedzie się, zostanie wywołana
procedura obsługi błędów, onDbError, która wyświetli błąd. Jeśli addGroceryItem powiedzie się, skrypt
wywoła getGroceryItems, a następnie showGroceryItems, by wyświetlić wynikowy zbiór wierszy zwróconych
z tabeli groceryitems. Rysunek 11.3 pokazuje wyniki po wprowadzeniu kilku elementów.
W funkcji getGroceryItems skrypt wykona metodę readTransaction, przekazując jej funkcję wywołującą
executeSql. Polecenie SQL wybiera z tabeli groceryitems wszystkie rekordy, grupuje je i sortuje alfabetycznie
według działów. Dodatkowo polecenie SQL łączy tabele groceryitems i departments na podstawie
identyfikatora deptid, tak że zwracany zbiór będzie również zawierać nazwę działu do wyświetlenia. Jeśli
wywołanie executeSql powiedzie się, skrypt zwróci wyniki do showGroceryItems, przekazując odwołanie
do transakcji i zbiór rekordów z wynikami. Aby wyświetlić wyniki ze zbioru rekordów, skrypt przechodzi
w pętli przez wiersze tego zbioru. Każdy wiersz reprezentuje rekord zwracany przez zapytanie SQL,
a do każdego pola możesz się odwołać przez notację z kropką.
Podsumowanie 277

Rysunek 11.3. Przykładowe wyniki po dodaniu kilku produktów spożywczych

Aby usunąć produkt, odwiedzający klika przycisk X, który wywołuje metodę deleteItem, przekazując
jej identyfikator produktu. Następnie strona używa przekazanego identyfikatora produktu, aby usunąć
produkt z tablicy groceryitems. Jeśli wywołanie się powiedzie, lista jest odświeżana poprzez funkcje
pobierania i pokazywania produktów spożywczych. Odwiedzający może także wyczyścić listę zakupów,
co spowoduje usunięcie wszystkich produktów z tabeli groceryitems.
Ten przepis jest prostą próbą integracji sieciowych baz danych klienta z Twoimi witrynami i aplikacjami.
Przepis obejmuje pisanie, wyszukiwanie, aktualizowanie i usuwanie rekordów wraz z początkowym
tworzeniem bazy danych. Ta funkcjonalność zapewnia elementy potrzebne do zarządzania przechowywaniem
danych w bazie danych.

Podsumowanie
W tym rozdziale poznałeś kilka nowych interesujących sposobów przechowywania danych w przeglądarce
klienta. Wcześniej technologia przeglądarek klienckich była ograniczona do korzystania z ciasteczek. Dzięki
dodaniu pamięci sesji, pamięci lokalnej oraz sieciowych baz danych masz teraz kilka możliwości zapisu
prostych par klucz-wartość albo złożonych struktur danych. Przy użyciu tych sposobów przechowywania
danych możesz tworzyć zaawansowane aplikacje w trybie offline, zmniejszać liczbę żądań pobrania danych
z serwera oraz przechowywać informacje pomiędzy sesjami przeglądarki po stronie klienta.
278 Rozdział 11. Przechowywanie danych po stronie klienta
12
Komunikacja i wątki

API HTML5 obejmują wiele nowych obszarów i ulepszeń, a dwa spośród najnowszych to WebSocket
API i Web Workers API, które wypełniają istotne luki dotyczące tworzenia witryn i aplikacji
internetowych. Przed ich pojawieniem się otwarcie dwukierunkowego kanału komunikacji z serwerami
stanowiło problem, a intensywne przetwarzanie na stronie dodatkowo uniemożliwiało interfejsowi interakcję
z użytkownikiem. WebSocket API dzięki połączeniu poprzez gniazdo sieciowe udostępnia dwukierunkową
komunikację między JavaScriptem klienta a serwerem, natomiast Web Workers API udostępnia JavaScriptowi
klienta podstawowe możliwości pracy wielowątkowej. W tym rozdziale dowiesz się, jak wdrożyć oba te
interfejsy — w tym celu zrealizujesz kilka prostych, ale skutecznych przepisów, na których możesz oprzeć
własne rozwiązania.

Przegląd WebSocket API


WebSocket API udostępnia nowy sposób komunikacji z serwerami dzięki przesyłaniu wiadomości bezpośrednio
poprzez gniazda sieciowe. Komunikacja jest dwukierunkowa — informacje mogą być przesyłane ze strony
klienta do serwera lub z serwera do strony w przeglądarce w dowolnym momencie, gdy połączenie jest otwarte.
Gniazdo sieciowe korzysta z oddzielnego protokołu serwerowego, bez narzutu związanego z protokołem
HTTP, więc pakiety mają kolejną zaletę w postaci niewielkich rozmiarów. Domyślnie jednak, aby aktywować
połączenie z gniazdem sieciowym, należy uruchomić na serwerze odpowiednią usługę gniazd sieciowych,
do której będą wysyłane wiadomości i z której będą odbierane. Usługi takie mogą być napisane w najróżniejszych
językach, a przed HTML5 podobne możliwości komunikacji były dostępne tylko poprzez instalacje
wyspecjalizowanych obiektów, trwałe połączenia typu comet albo częste zapytania przesyłane przez klienta
do serwera poprzez sieć.
Aby utworzyć połączenie z gniazdem sieciowym, skrypt tworzy instancję interfejsu WebSocket, przekazując
jej adres URL usługi sieciowej, jak pokazano poniżej:
var myWS = new WebSocket("ws://jakas.usluga.sieciowa.com/");

Po utworzeniu instancji obiekt WebSocket próbuje otworzyć połączenie z usługą nasłuchującą pod adresem
URL podanym w konstruktorze obiektu. Jeżeli połączenie zostanie pomyślnie otwarte, instancja obiektu
WebSocket udostępni następujące metody do wysyłania wiadomości i zamykania połączeń:
„ send(DOMString) — wysyła dane w postaci ciągu znaków,
„ send(ArrayBuffer) — wysyła dane w postaci ArrayBuffer,
280 Rozdział 12. Komunikacja i wątki

„ send(Blob) — wysyła dane w postaci typu Blob,


„ close([kod][,przyczyna]) — zamyka połączenie z gniazdem z opcjonalnym kodem typu
unsigned long i ciągiem DOMString określającym przyczynę.

Oto zdarzenia dostępne w obiekcie WebSocket:


„ onopen — połączenie jest otwarte i gotowe,
„ onclose — połączenie jest zamknięte,
„ onmessage — została odebrana wiadomość,
„ onerror — wystąpił błąd połączenia.

W celu ustalenia, czy połączenie z gniazdem umożliwia wysłanie danych, możesz wykorzystać zdarzenie
onopen albo atrybut połączenia readyState, który może przyjąć jedną z czterech wymienionych w tabeli 12.1
wartości.

Tabela 12.1. Wartości atrybutu readyState interfejsu WebSocket

Wartość Stała Opis


0 CONNECTING Próba nawiązania połączenia.
1 OPEN Połączenie jest otwarte i gotowe.
2 CLOSING Połączenie jest zamykane.
3 CLOSED Połączenie jest zamknięte.

Tabela 12.2 przedstawia wersje najpopularniejszych przeglądarek obsługujące WebSocket API. Należy
zauważyć, że w różnych przeglądarkach obsługa ta wygląda odmiennie, do czego wrócimy przy okazji
omawiania konkretnych przepisów w dalszej części tego rozdziału.

Tabela 12 2. Obsługa WebSocket API w przeglądarkach

Android -
Chrome 10.0+
Firefox 4.0+
Internet Explorer -
iOS Safari 4.2+
Opera 11.0+
Safari 5.0+

PRZEPIS DLA POCZĄTKUJĄCYCH


Rozmawianie przez gniazda sieciowe
W tym przepisie wykorzystamy interfejs WebSocket do otwarcia połączenia z serwerem przez gniazdo sieciowe,
wysłania wiadomości wpisanej przez użytkownika i otrzymania echa tej samej wiadomości z serwera.
Skorzystamy przy tym z dostępnej pod adresem www.websocket.org darmowej usługi testowania gniazd
sieciowych, która ułatwia testowanie skryptów gniazd sieciowych poprzez sprawdzanie, czy można otworzyć
Rozmawianie przez gniazda sieciowe 281

połączenie oraz wysyłać i odbierać wiadomości. Aby utworzyć połączenie, wykonaj następujące kroki,
co pozwoli Ci utworzyć kod z listingu 12.1:
1. Utwórz pustą stronę HTML z elementem input dla wiadomości, przyciskami do wysyłania
wiadomości i zamykania połączenia oraz elementami div dla statusu i otrzymanej wiadomości.
2. Dodaj zmienne globalne dotyczące gniazda sieciowego i adresu URL.
3. Dodaj funkcję init i procedurę nasłuchu zdarzenia załadowania strony.
4. Dodaj funkcje onOpen, onClose, onMessage i onError do obsługi zdarzeń połączenia poprzez
gniazdo sieciowe.
5. Dodaj funkcje postMessage, closeWS i updateStatus do obsługi wysyłania wiadomości, zamknięcia
połączenia i aktualizowania statusu połączenia.

Listing 12.1. Wysyłanie i otrzymywanie danych przez gniazda sieciowe

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>12.1. Komunikacja poprzez gniazdo sieciowe</title>
<script>
// ustaw globalne odwołanie do gniazda sieciowego
var directorWebSocket = null;

// przypisz adres procesu nasłuchu gniazda sieciowego


var wsUri = 'ws://echo.websocket.org/';

// po załadowaniu strony utwórz połączenie przez gniazdo sieciowe


function init()
{
// dodaj procedury obsługi zdarzeń przycisków
var btnSend = document.getElementById('btnSend');
var btnClose = document.getElementById('btnClose');

btnSend.addEventListener('click',postMessage,false);
btnClose.addEventListener('click',closeWS,false);

updateStatus('Inicjalizacja połączenia przez gniazdo sieciowe');

// utwórz instancję gniazda sieciowego z adresem nasłuchu


directorWebSocket = new WebSocket(wsUri);

// skonfiguruj funkcje obsługi dla zdarzeń gniazd sieciowych


directorWebSocket.onopen = function(evt) { onOpen(evt) };
directorWebSocket.onclose = function(evt) { onClose(evt) };
directorWebSocket.onmessage = function(evt) { onMessage(evt) };
directorWebSocket.onerror = function(evt) { onError(evt) };
}

// PROCEDURY OBSŁUGI ZDARZEŃ GNIAZD SIECIOWYCH

// połączenie przez gniazdo sieciowe pomyślnie otwarte


function onOpen(evt) {
console.log('Połączenie zarządzające otwarte');
updateStatus('Połączenie otwarte');
};
282 Rozdział 12. Komunikacja i wątki

// otrzymano wiadomość poprzez połączenie przez gniazdo sieciowe


function onMessage(evt) {
console.log('Otrzymano wiadomość: ' + evt.data);
updateStatus('Otrzymana wiadomość: ' + evt.data);
document.getElementById('messages').innerHTML = evt.data;
};

// otrzymano błąd z gniazda sieciowego


function onError(evt) {
console.log('Błąd połączenia zarządzającego: ' + evt.data);
updateStatus('Błąd: '+ evt.data);
};

// połączenie przez gniazdo sieciowe pomyślnie zamknięte


function onClose(evt) {
console.log('Połączenie zarządzające zamknięte.');
updateStatus('Połączenie zamknięte.');
};

// FUNKCJE KLIENTA

// wyślij wiadomość poprzez połączenie przez gniazdo sieciowe


function postMessage() {

// pobierz komunikat z elementu input


msg = document.getElementById('msg').value;

console.log('Wysyłanie wiadomości przez gniazdo sieciowe: ' + msg);


updateStatus('Wysyłanie wiadomości: ' + msg);

// użyj send() do wysłania wiadomości


directorWebSocket.send(msg);
}

// zamknij gniazdo sieciowe


function closeWS() {

console.log('Rozłączanie połączenia przez gniazdo sieciowe');


updateStatus('Rozłączanie');

// nakaż instancji gniazda sieciowego zamknąć połączenie


directorWebSocket.close();
}

// funkcja pomocnicza do zmieniania statusu gniazda sieciowego


function updateStatus(msg) {
document.getElementById('wsState').innerHTML = msg;
}

// dodaj procedurę nasłuchu zdarzenia załadowania strony, aby uruchomić funkcję init
window.addEventListener('load', init, false);

</script>
</head>
<body>
<div id="btnTryCall">
<input type="text" id="msg" />
<button id="btnSend">Wyślij wiadomość</button>
<button id="btnClose">Zamknij gniazdo sieciowe</button>
</div>
Realizacja wielowątkowości za pomocą wątków roboczych 283

<div id="wsState"></div>
<div id="messages"></div>
</body>
</html>

Po załadowaniu strony z listingu 12.1 jest wywoływana funkcja init. W tej funkcji skrypt dodaje procedury
nasłuchu zdarzeń przycisków, a następnie buduje połączenie przez gniazdo sieciowe, tworząc instancję
interfejsu WebSocket i przekazując jej adres. Po utworzeniu tej instancji zdarzenia onopen, onclose, onmessage
i onerror są rejestrowane — każdemu z nich zostaje przyporządkowana odpowiednia funkcja. Po pomyślnym
otwarciu połączenia przez gniazdo sieciowe zostaje wywołane zdarzenie onopen, a w konsekwencji funkcja
onOpen, która zmienia wyświetlany na stronie status połączenia. W tym przepisie wpisaliśmy do dziennika
konsoli komunikaty, tak abyś w dzienniku swojej przeglądarki mógł śledzić przepływ danych poprzez
połączenie używające gniazda sieciowego.
Strona wyświetla użytkownikowi pole wprowadzania danych, w którym może on wpisać wiadomość
i wysłać ją za pośrednictwem gniazda sieciowego do serwera. Wiadomość jest wysyłana w funkcji postMessage,
w której na gnieździe sieciowym zostaje wywołana metoda send z komunikatem przekazanym w parametrze.
Po odebraniu komunikatu usługa gniazda sieciowego przepakowuje ciąg znaków i wysyła go z powrotem
do strony w postaci wiadomości. Procedura obsługi zdarzenia onmessage przechwytuje przychodzącą
wiadomość, a odpowiadająca mu funkcja onMessage pobiera przychodzący komunikat i aktualizuje zawartość
ekranu częścią komunikatu zawierającą dane.
W przypadku wystąpienia błędu podczas otwierania, używania albo zamykania połączenia przez gniazdo
sieciowe procedura obsługi zdarzenia onerror przechwyci błąd i wyświetli użytkownikowi fragment błędu
zawierający dane. Po zakończeniu konwersacji pomiędzy stroną a usługą gniazda sieciowego można zamknąć
połączenie poprzez gniazdo sieciowe, wywołując na nim metodę close.

Uwaga
Usługa, której Twoja instancja gniazda sieciowego wysyła wiadomości i od której je odbiera, będzie
zwykle zaprojektowana przez Ciebie i będzie działać na Twoim serwerze sieciowym. W sieci możesz
znaleźć kilka przykładów napisanych w różnych językach programowania i mających otwarte źródła,
dotyczących konfigurowania procesu serwera gniazda sieciowego.

W tym przepisie otworzyliśmy połączenie ze zdalnym serwerem i wysłaliśmy w obie strony wiadomości
przy wykorzystaniu połączenia przez gniazdo sieciowe. Może to być niezwykle przydatne do
dwukierunkowej komunikacji, a zwłaszcza inicjowanego przez usługę przesyłu wiadomości do podłączonej
z nią strony przeglądarki.

Realizacja wielowątkowości
za pomocą wątków roboczych
Powszechnym problemem związanym z działającym na stronie HTML JavaScriptem jest ograniczenie
jego działania do jednego wątku, co może skutkować zablokowaniem interfejsu podczas intensywnego
przetwarzania. Dzięki Web Workers API w HTML5 programiści mają możliwość odseparowania
przetwarzania i wykonywania zadań w działającym w tle wątku mającym niższy priorytet od głównego
wątku na stronie HTML. W ten sposób główny wątek pozostaje odblokowany i umożliwia użytkownikowi
interakcję ze stroną z zerowym lub minimalnym negatywnym wpływem na stronę.
284 Rozdział 12. Komunikacja i wątki

Implementacja Web Workers API w HTML5 udostępnia dwa rodzaje wątków roboczych: dedykowane
i współdzielone. Dedykowany wątek roboczy jest przypisany do strony, która go uruchamia, i nie jest
dostępny z poziomu innych stron. Współdzielony wątek roboczy może być z kolei dzielony przez wiele
stron z tej samej domeny w tej samej instancji przeglądarki. Wątek współdzielony ma swój własny stan,
również dzielony przez strony, co zobaczysz w ostatnim przepisie tego rozdziału.
Czym więc konkretnie jest wątek roboczy? Wątek roboczy jest wyodrębnionym w formie pliku
javascriptowym skryptem uruchamianym w nowym wątku. Zamiast przechowywać taki skrypt w osobnym
pliku .js, można go również przekazać wątkowi roboczemu bezpośrednio w parametrze. Wątek roboczy
może się dwukierunkowo komunikować z uruchamiającą go stroną oraz głównym skryptem za pomocą
wiadomości, które są przesyłane pomiędzy wątkami.

Wskazówka
Wątki robocze nie mogą manipulować elementami DOM strony, ponieważ wątek roboczy działa w wątku
oddzielonym od samej strony. DOM może być modyfikowany tylko z poziomu głównego wątku strony.

Aby powstała instancja wątku roboczego, należy utworzyć nową instancję interfejsu Worker, przekazując
jej skrypt lub plik do wykonania, co pokazano poniżej na przykładzie dedykowanego wątku roboczego:
var myWorker = new Worker('worker_script.js');

Dedykowany wątek roboczy z powyższego kodu po utworzeniu wykona kod javascriptowy. Wątek może
nieustannie działać i być dostępny, dopóki nie zostanie zakończony lub nie zostanie zamknięta strona, która
go utworzyła. Oto metody dostępne dla dedykowanego wątku roboczego:
„ postMessage(wiadomość [, portWiadomości]) — wysyła wiadomość do wątku roboczego.
Wątek roboczy „przechwytuje” komunikat za pomocą opisanego niżej zdarzenia onmessage.
„ terminate() — kończy wątek roboczy, na którym ta metoda jest wywoływana.

W dedykowanym wątku roboczym dostępne są dwa zdarzenia: onmessage i onerror. Zdarzenie onmessage
pozwala głównemu skryptowi strony otrzymywać wiadomości z wątku roboczego, a wątkowi roboczemu
— wiadomości z głównego wątku. W tabeli 12.3 przedstawiono zdarzenia onmessage i onerror dostępne
w interfejsie Worker.

Tabela 12.3. Zdarzenia wątków roboczych

Nazwa zdarzenia Atrybut Przeznaczenie


message onmessage Wyzwalane, gdy wiadomość zostanie odebrana. Obiekt zdarzenia ze składową
data będzie dostarczony z wiadomością.
error onerror Wyzwalane, gdy wystąpi błąd w wątku roboczym. Zdarzenie udostępnia
składową data z informacją o błędzie.

Uwaga
Jeśli programowałeś kiedyś w języku, który obsługuje korzystanie z wielu wątków, prawdopodobnie jesteś
świadomy zagadnień bezpieczeństwa wątków i problemów, które mogą powstać przy równoczesnym
ich uruchamianiu. W Web Workers API w większości przypadków przed problemami chroni przesyłanie
wiadomości między wątkami za pomocą serializowanych obiektów. W celu uniknięcia pozostałych
problemów wątki robocze nie mają dostępu do struktury DOM strony ani do składników, które nie są
bezpieczne dla wątków. Tylko wątek głównej strony może zaktualizować elementy DOM strony.
Tworzenie wątku roboczego 285

W tabeli 12.4 przedstawiono obsługę Web Workers API przez najpopularniejsze przeglądarki.

Tabela 12.4. Dostępność Web Workers API w przeglądarkach

Android 2.1
Chrome 10.0+
Firefox 3.6+
Internet Explorer 10.0+
iOS Safari -
Opera 10.6+
Safari 4.0+

Uwaga: po wersji 2.1 z przeglądarki Android usunięto obsługę wątków roboczych.

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie wątku roboczego
W tym przepisie utworzymy prosty dedykowany wątek roboczy w celu zaimplementowania w nim w tle
prymitywnej metody sumowania liczb z pewnego zakresu. Po załadowaniu strony HTML wątek roboczy
zostanie uruchomiony. W miarę jak będzie on sumował kolejne liczby z zakresu, do głównego skryptu będą
dostarczane informacje o procentowym postępie pracy. Kiedy wątek roboczy zakończy sumowanie liczb,
wyśle do głównego skryptu wiadomość zawierającą końcowy wynik. Na stronie znajdzie się również przycisk
kończący działanie wątku, dzięki czemu użytkownik będzie mógł w każdej chwili go zakończyć.
Wątek roboczy będzie w opisany sposób wykonywał polecenia w wątku, który nie blokuje skryptów ani
przetwarzania głównej strony. Aby to zademonstrować, strona zawiera przycisk znacznika czasu, pozwalający
zapisać w dzienniku strony znacznik czasowy — w miarę postępu pracy wątku spróbuj kliknąć przycisk
znacznika czasu, aby sprawdzić, czy główna strona nie jest blokowana przez działania wątku roboczego. Przepis
obejmuje dwa pliki: stronę HTML z listingu 12.2 oraz plik javascriptowy wątku roboczego z listingu 12.3.
1. Utwórz stronę z listingu 12.2 ze znacznikiem output, przyciskami i sekcją przeznaczoną
na wpisy dziennika.
2. Dodaj do strony fragment zawierający skrypt, w tym zmienną wątku i funkcję inicjalizacji
startWorkerThread.
3. Dodaj funkcję terminateWorker umożliwiającą zakończenie pracy wątku roboczego.
4. Dodaj funkcję getTimeStamp i wyzwalacz window.addEventListener.

Listing 12.2. Przesyłanie wiadomości do wątku roboczego

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>12.2. Prosta komunikacja z wątkiem roboczym</title>
<script>
// globalne odwołanie do wątku roboczego
var worker = null;
286 Rozdział 12. Komunikacja i wątki

// po załadowaniu strony rozpocznij wątek roboczy


function startWorkerThread() {

// dodaj procedury obsługi zdarzeń przycisków


var btnTimeStamp = document.getElementById('btnTimeStamp');
var btnTerminateWorker = document.getElementById('btnTerminateWorker');

btnTimeStamp.addEventListener('click',getTimeStamp,false);
btnTerminateWorker.addEventListener('click',terminateWorker,false);

// utwórz instancję wątku roboczego


worker = new Worker('listing.12.3.js');

// przypisz procedurę obsługi do odbierania wiadomości z wątku roboczego


worker.onmessage = function (event) {
// wyświetl komunikat w polu wyników
document.getElementById('result').textContent = event.data;
};
}

// obsłuż przycisk służący do zakończenia pracy wątku roboczego


function terminateWorker() {

// nakaż wątkowi roboczemu zakończyć działanie


worker.terminate();
}

// lokalny wątek, mający zapisać znacznik czasowy i pokazać, że główny wątek nie jest blokowany przez trwające obliczenia
function getTimeStamp() {

// pobierz aktualną datę i godzinę oraz dodaj je do dziennika czasu


var currentDateTime = new Date();
document.getElementById('timeLog').innerHTML += currentDateTime+ '<br>';
}

// inicjalizuj wątek roboczy po załadowaniu strony


window.addEventListener('load',startWorkerThread,false);

</script>
</head>
<body>
<p>Obliczanie <output id="result"></output></p>
<button id="btnTimeStamp">Znacznik czasowy</button>
<button id="btnTerminateWorker">Zakończ wątek roboczy</button>
<section id="timeLog"></section>
</body>
</html>

Kiedy strona tworzy wątek roboczy, nowej instancji jest przekazywana nazwa javascriptowego pliku do
wykonania w nowym wątku. W tym przypadku plik nazywa się listing.12.3.js. Listing 12.3 przedstawia kod tego
pliku, który przechodzi w pętli przez zakres liczb, dodaje je i wysyła wiadomości do głównego skryptu strony.
1. Utwórz javascriptowy plik z listingu 12.3, dodając zmienne na jego początku.
2. Dodaj instrukcję for przechodzącą w pętli od 1 do maxLimit, sumującą liczby i zwracającą
procentowy status ukończenia.
3. Dodaj końcowe wywołanie postMessage, aby przesłać całkowitą sumę do głównego skryptu strony.
Tworzenie wątku roboczego 287

Listing 12.3. Wykonywanie zadania w wątku roboczym

// 12.3. Prosty plik wątku roboczego

// inicjalizuj zmienne
var sum = 0;
var currentPercentageComplete = 0;
var maxLimit = 100000000;

// w pętli
for (var j=0; j<=maxLimit; j++) {

// wykonaj długi sposób sumowania


sum+=j;

// określ procent ukończenia


newPercentageComplete = Math.round((j/maxLimit)*100);

// minimalizuj liczbę wysyłanych wiadomości poprzez wysyłanie ich tylko wtedy, gdy procent się zmienił
if (newPercentageComplete > currentPercentageComplete) {

// odeślij wiadomość do głównego wątku strony


postMessage('ukończono w ' + newPercentageComplete + '%');

// aktualizuj bieżący procent ukończenia


currentPercentageComplete = newPercentageComplete;
}
}

// na koniec prześlij uzyskaną wartość sumy do głównego wątku strony


postMessage('Suma = ' + sum);

Po załadowaniu do przeglądarki głównej strony zdarzenie load okna wywołuje funkcję startWorkerThread.
Funkcja dodaje procedury nasłuchu przycisków, tworzy nową instancję Worker, przekazuje jej nazwę pliku
javascriptowego oraz przypisuje odwołanie do niej zmiennej wątku. Po utworzeniu tej instancji skrypt definiuje
funkcję, która po wystąpieniu zdarzenia onmessage otrzyma dane i w elemencie output o identyfikatorze result
wyświetli komunikat pochodzący z wątku roboczego. Kiedy wątek roboczy wyśle za pomocą postMessage
wiadomość do głównego skryptu, zostaje uruchomiona procedura obsługi zdarzenia onmessage.
Kiedy instancja wątku zostaje utworzona, skrypt jest ładowany do wątku i automatycznie uruchamiany.
Z kolejnego przepisu dowiesz się, jak ręcznie poinstruować skrypt, kiedy ma rozpocząć działanie po
utworzeniu wątku.
W miarę wykonywania skryptu w wątku roboczym poprzez przechodzenie w pętli przez kolejne liczby
obliczana jest całkowita suma (dla celów demonstracyjnych w bardzo długi i żmudny sposób). W miarę
postępów pętli skrypt sprawdza, czy zwiększył się procent ukończenia. Jeżeli procent ukończenia uległ zmianie,
skrypt za pomocą polecenia postMessage wysyła głównej stronie wiadomość zawierającą nową wartość
procentowego zakończenia. Wiadomość ta uruchamia procedurę obsługi zdarzenia onmessage w głównym
skrypcie strony. Główny skrypt pobiera wówczas zdarzenie i uzyskuje dostęp do składowej data, która
zawiera właściwy tekst. Zostaje wyświetlony procent ukończenia, jak pokazano na rysunku 12.1.
Jeśli użytkownik chciałby zakończyć wątek roboczy bez zamykania strony, może użyć przycisku
zakończenia wątku i w ten sposób poinformować wątek za pomocą metody terminate, żeby natychmiast
zakończył pracę.
288 Rozdział 12. Komunikacja i wątki

Rysunek 12.1. Przykładowe znaczniki czasowe wyświetlone podczas wykonywania przez wątek obliczeń

Wskazówka
Tworzenie i wykonywanie wątków roboczych może stanowić spore obciążenie. Najlepiej korzystać
z nich, gdy są „długowieczne” i wykonują operacje, które nie powinny blokować głównego wątku
interfejsu. Powinieneś się zastanowić, kiedy używać wątku roboczego i jak wiele wątków należy
utworzyć. Nie istnieje odgórny limit liczby wątków roboczych, które możesz utworzyć, a wątki mogą
nawet tworzyć instancje innych wątków, jednak z każdym kolejnym wątkiem roboczym związane jest
pewne obciążenie, zatem najlepiej jest ograniczać ich liczbę do minimum.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Dodawanie dwukierunkowej komunikacji
Z poprzedniego przepisu dowiedziałeś się, w jaki sposób główny skrypt może utworzyć dedykowany
wątek roboczy i jak wątek ten może wysyłać wiadomości do głównego skryptu. Przesyłanie wiadomości
zaprojektowano tak, aby możliwa była komunikacja pomiędzy wątkami, co dotyczy również głównego
wątku strony, który utworzył instancję wątku roboczego. Oznacza to, że główny skrypt i wątek roboczy
mogą sobie nawzajem wysyłać wiadomości za pośrednictwem metody postMessage interfejsu Worker.
W tym przepisie główny skrypt wyśle informacje do wątku roboczego, a ten z kolei prześle potwierdzenie
otrzymania wiadomości do głównego skryptu.
W tym przepisie zajmiemy się również obsługą błędów wątku roboczego poprzez zdarzenie error oraz
za pomocą niestandardowych komunikatów zwracanych do głównego skryptu. Komunikat wysyłany przez
metodę postMessage ma format serializowanego obiektu, co oznacza, że w celu hermetyzacji wielu składowych
danych można wysłać napisy w formacie JSON. Ten sposób przesyłania złożonych danych wykorzystamy
w celu poinformowania głównego skryptu o typie przesyłanej wiadomości i jej zawartości. Ponadto procedura
obsługi zdarzeń onerror jest zaprogramowana tak, aby obsługiwała dowolne javascriptowe błędy, w czasie
gdy przetwarzany jest skrypt wątku roboczego. Aby rozpocząć, wykonaj następujące kroki, jak pokazano
na listingu 12.4:
1. Utwórz pusty plik HTML i dodaj znacznik body, który zawiera pole tekstowe input, przycisk
do wysyłania wiadomości i sekcję służącą do wyświetlania zwróconej wiadomości.
2. Dodaj znacznik script z deklaracją wątku roboczego i procedurą obsługi onmessage.
3. Dodaj procedurę obsługi onerror, umożliwiającą przechwytywanie wszelkich błędów wątku
roboczego.
4. Dodaj funkcję postToWorker, aby za pośrednictwem metody postMessage wysłać wprowadzany
tekst do wątku roboczego.

Listing 12.4. Tworzenie strony klienta

<!DOCTYPE html>
<html>
<head>
Dodawanie dwukierunkowej komunikacji 289

<meta charset="UTF-8" />


<title>12.4. Komunikacja między wątkami roboczymi</title>
<script>
// utwórz wątek roboczy
var worker = new Worker('listing.12.5.js');

// utwórz procedurę obsługi dla wiadomości z wątku roboczego


worker.onmessage = function (event) {

// pobierz część wiadomości zawierającą dane


var msgFromWorker = event.data;

// sprawdź typ wiadomości


switch (msgFromWorker.msgType) {

case 'MSG':
// wiadomość wysłana z wątku roboczego — wyświetlanie
var workerResponse = document.getElementById('workerResponse');
workerResponse.innerHTML = 'Wątek roboczy przesłał: ' + msgFromWorker.msg;
break;

case 'ERR':
// błąd wysłany z wątku roboczego — powiadom użytkownika
alert('Błąd z wątku roboczego: ' + msgFromWorker.msg);
break;
}
};

// procedura obsługi błędów dla wątku roboczego


worker.onerror = function (error) {
// po prostu powiadom użytkownika o błędzie
alert('Błąd z wątku roboczego: ' + error.message);
};

// wyślij dane wejściowe do wątku roboczego


function postToWorker() {
worker.postMessage(document.getElementById('inputForWorker').value);
}

// inicjalizuj procedury obsługi


function init() {
var btnPostToWorker = document.getElementById('btnPostToWorker');
btnPostToWorker.addEventListener('click',postToWorker,false);
}

window.addEventListener('load',init,false);

</script>
</head>
<body>
<p>Wpisz wiadomość tekstową dla wątku roboczego (pozostaw puste pole, aby wątek zwrócił wiadomość
´zawierającą błąd):</p>
<input id='inputForWorker' />
<button id="btnPostToWorker">Wyślij do wątku roboczego</button>
<section id="workerResponse"></section>
</body>
</html>
290 Rozdział 12. Komunikacja i wątki

Tak jak poprzednio, do przechowywania kodu wątku roboczego wykorzystamy zewnętrzny plik
javascriptowy. Treść tego pliku jest przedstawiona na listingu 12.5, który można odtworzyć, wykonując
poniższe kroki. Upewnij się, że użyta nazwa pliku jest taka sama jak na listingu 12.4, w poleceniu tworzenia
wątku roboczego w Twoim głównym skrypcie.
1. Utwórz pusty plik i dodaj procedurę obsługi zdarzenia onmessage z listingu 12.5.
2. Dodaj polecenie warunkowe if i jego gałęzie w celu sprawdzenia prawidłowości danych
wejściowych oraz odesłania do głównego skryptu błędu albo wiadomości z potwierdzeniem.

Listing 12.5. Dodawanie wątku roboczego

// 12.5. Plik js dwukierunkowego wątku roboczego

// przechwyć wiadomości wysłane do wątku roboczego


onmessage = function(event) {

// sprawdź, czy dane są puste, i wyślij z powrotem błąd


if (event.data === '') {
// prześlij do klienta wiadomość z informacją o błędzie
postMessage({msgType:'ERR',msg:'Nieprawidłowy wpis z danymi'});
} else {
// prześlij do klienta wiadomość potwierdzającą odbiór
newMessage = 'Wątek roboczy otrzymał "' + event.data + '"';
postMessage({msgType:'MSG',msg:newMessage});
}
}

Poprzez załadowanie javascriptowego pliku wątku po wczytaniu strony HTML zostaje utworzony wątek
roboczy. Zauważ jednak, że w odróżnieniu od wcześniejszego przepisu, bezpośrednio po utworzeniu wątek
ten nie wykonuje żadnych działań. Wątek roboczy czeka, dopóki przychodząca wiadomość nie uruchomi
procedury obsługi wiadomości. Na stronie HTML użytkownik jest proszony o wpisanie w polu tekstowym
ciągu tekstowego i kliknięcie przycisku Wyślij do wątku roboczego. Przycisk ten uruchamia funkcję postMessage,
wysyłającą łańcuch tekstowy do wątku roboczego. Wątek roboczy sprawdza następnie, czy łańcuch jest
pusty — jeśli tak, wysyła z powrotem do głównego skryptu wiadomość przy użyciu łańcucha JSON z typem
komunikatu ERR i odpowiednią informacją. Jeśli otrzymana przez wątek roboczy wiadomość nie jest pusta,
wątek roboczy opakowuje łańcuch innym łańcuchem, aby oznaczyć to, co zostało odebrane, i wysłać
z powrotem zaktualizowaną wiadomość w tym samym formacie JSON, ale z typem komunikatu MSG.
Po odesłaniu błędu lub wiadomości do głównego skryptu jest w nim uruchamiana procedura obsługi
zdarzenia onmessage. Funkcja sprawdza typ przesłanego komunikatu, a następnie albo informuje użytkownika
o błędzie, albo aktualizuje pole wiadomością z wątku roboczego (co pokazano na rysunku 12.2), po czym
kończy pętlę komunikacyjną.

Rysunek 12.2. Przykładowy rezultat pokazujący wiadomość wysłaną i zwróconą przez wątek roboczy

Aby uzyskać komunikat o błędzie, pozostaw pole wejściowe puste i kliknij przycisk Wyślij do wątku
roboczego, co spowoduje wysłanie do wątku roboczego pustego ciągu znaków.
Wykorzystywanie współdzielonych wątków roboczych 291

W tym oraz poprzednim przepisie wykorzystaliśmy dedykowane wątki robocze, które są dostępne
wyłącznie dla tworzących je stron. W niektórych przypadkach możesz jednak potrzebować wątku roboczego
współdzielonego przez strony. W kolejnym przepisie wyjaśnimy strukturę współdzielonego wątku roboczego
oraz sposób zaimplementowania połączenia z takim wątkiem.

PRZEPIS DLA ZAAWANSOWANYCH


Wykorzystywanie współdzielonych wątków roboczych
Jak dowiedziałeś się w poprzednich przepisach, obszarem działania dedykowanego wątku roboczego jest
strona, która go utworzyła. Taki wątek roboczy jest wykonywany w sposób całkowicie wyizolowany, więc
jego działania są w jego środowisku bezpieczne wątkowo. Web Workers API w HTML5 obejmuje jednak
także drugi interfejs wątków roboczych — SharedWorker. Interfejs ten udostępnia tę samą funkcjonalność
wątku roboczego, ale umożliwia jego współdzielenie przez wiele stron z tej samej domeny w tej samej
przeglądarce. Tak jak w przypadku dedykowanego wątku roboczego, funkcjonalność we współdzielonym
wątku roboczym może być dla każdego żądania niezależna, może on jednak współdzielić swoje środowisko
pomiędzy stronami, o czym przekonasz się w niniejszym przepisie.
Ponieważ instancja współdzielonego wątku roboczego może mieć wiele klientów komunikujących się
z tym wątkiem, interfejs różni się nieco od interfejsu dedykowanego wątku roboczego. Współdzielony wątek
roboczy w celu identyfikacji połączenia strony z wątkiem roboczym wykorzystuje przypisanie do portu.
Przypisanie to jest używane przez współdzielony wątek roboczy do identyfikacji źródła wiadomości oraz
jej adresata. SharedWorker jest tworzony w następujący sposób:
var sworker = new SharedWorker('mySharedWorker.js');

Współdzielony wątek roboczy ma jedno zdarzenie, connect, które jest generowane, gdy wątek klienta
łączy się ze współdzielonym wątkiem roboczym, co opisano w tabeli 12.5.

Tabela 12.5. Zdarzenie SharedWorker

Nazwa zdarzenia Atrybut Przeznaczenie


connect onconnect Wyzwalane, gdy wiadomość zostanie odebrana. Obiekt zdarzenia ze składową
data będzie dostarczony z wiadomością.

Jak wspomniano, każde połączenie klienta jest jednoznacznie identyfikowane poprzez port. Metoda
postMessage i zdarzenia wiadomości wykorzystują ten port, tak aby przesyłanie wiadomości odbywało się
na poziomie połączenia.
W tym przepisie wyjaśnimy, jak wykorzystać współdzielenie wątku roboczego na wielu stronach do
obliczenia średniej wartości liczbowej na podstawie wartości podanych poprzez dowolną z podłączonych
stron. Kiedy średnia ulega zmianie, wynik — w celu wyświetlenia — jest wysyłany do każdej strony. Dla celów
testowych strony klienta są wyświetlane przy użyciu iframe w jednym dokumencie, który pełni funkcję
pojemnika, zamiast w osobnych oknach przeglądarki. Mamy zatem trzy pliki do utworzenia: główną stronę
(pojemnik), która przechowa ramkę iframe dla każdej strony klienta, stronę klienta, która będzie przyjmować
dane wejściowe od użytkownika i łączyć się ze współdzielonym wątkiem roboczym, oraz właściwy plik
javascriptowy współdzielonego wątku roboczego. Zacznijmy od pliku pojemnika pokazanego na listingu 12.6:
292 Rozdział 12. Komunikacja i wątki

1. Utwórz pustą stronę HTML ze znacznikiem body oraz trzema elementami iframe w znacznikach div.
2. Następnie dodaj znaczniki style i zdefiniuj styl znaczników div i iframe, aby pokazać ramki iframe
w trzech równych kolumnach w poprzek strony. Na tym kończy się plik pojemnika.

Listing 12.6. Tworzenie pojemnika stron klientów

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>12.6. SharedWorker na wielu stronach</title>
<!-- To jest tylko strona pojemnika do pokazywania wielu stron
używających współdzielonego wątku roboczego. Rezultat byłby taki sam,
gdyby strony były w osobnych zakładkach albo oknach w tej samej
przeglądarce. -->
<style>
div{float:left;width:33.3%;height:500px}
iframe{width:100%;height:100%}
</style>
</head>
<body>
<div>Strona klienta 1<iframe src="listing.12.7.html"></iframe></div>
<div>Strona klienta 2<iframe src="listing.12.7.html"></iframe></div>
<div>Strona klienta 3<iframe src="listing.12.7.html"></iframe></div>
</body>
</html>

Następnie, wykorzystując kod pokazany na listingu 12.7, należy utworzyć stronę klienta, która będzie
załadowana do każdego elementu iframe pojemnika i wyświetli użytkownikowi pole wejściowe do
wprowadzania liczby.
1. Utwórz pustą stronę HTML z pokazaną na listingu 12.7 zawartością znacznika body, obejmującą
znacznik input, przycisk Wyślij liczbę do współdzielonego wątku roboczego i element zawierający
dziennik.
2. Dodaj znaczniki script i globalne odwołanie do instancji współdzielonego wątku o nazwie
sworker. Będzie to instancja SharedWorker, służąca do wysyłania i otrzymywania wiadomości.
3. Dodaj wiersz window.addEventListener i funkcję init, która tworzy połączenie ze współdzielonym
wątkiem roboczym oraz rejestruje procedurę obsługi onmessage dotyczącą portu tego klienta
współdzielonego wątku roboczego.
4. Dodaj funkcję sendNumber, która pobiera wartość wejściową i przesyła liczbę do współdzielonego
wątku roboczego w celu przetwarzania.

Listing 12.7. Tworzenie stron klienta

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>12.7. Klient współdzielonego wątku roboczego</title>
<script>

// globalne odwołanie do współdzielonego wątku roboczego


Wykorzystywanie współdzielonych wątków roboczych 293

var sworker = null;

// inicjalizuj stronę i połączenie ze współdzielonym wątkiem roboczym


function init() {

// odwołanie do elementu output zawierającego dziennik


var logOutput = document.getElementById('log');

// dodaj procedurę nasłuchu przycisku


var btnSendNumber = document.getElementById('btnSendNumber');
btnSendNumber.addEventListener('click',sendNumber,false);

// utwórz odwołanie do współdzielonego wątku roboczego


sworker = new SharedWorker('listing.12.8.js');

// procedura obsługi onmessage dla komunikatów ze współdzielonego wątku roboczego


sworker.port.onmessage = function(msg) {

// wyświetl zależnie od typu komunikatu


switch (msg.data.msgType) {
case 'LOG':
// otrzymany komunikat dziennika, dodaj do dziennika
logOutput.innerHTML += msg.data.msgText + '<br>';
break;
case 'AVE':
// otrzymana nowa średnia wartość, aktualizuj średnią
var aveOutput = document.getElementById('average');
aveOutput.innerHTML = msg.data.aveValue;
// dodaj wpis dziennika
logOutput.innerHTML += msg.data.msgText + '<br>';
break;
}
}
}

// wyślij wprowadzoną liczbę do współdzielonego wątku roboczego


function sendNumber() {

// pobierz liczbę
var numToSend = document.getElementById('numberToSend').value;

// prześlij komunikat do współdzielonego wątku roboczego (zwróć uwagę na port)


sworker.port.postMessage(numToSend);

// wyczyść wartość pola wejściowego


document.getElementById('numberToSend').value = '';
}

// dodaj wywołanie inicjalizujące zdarzenia załadowania strony


window.addEventListener("load", init, false);

</script>
</head>
<body>
Bieżąca średnia: <output id="average"></output>
<br><br>
<input id="numberToSend" />
<button id="btnSendNumber">Wyślij liczbę do współdzielonego wątku roboczego</button>
<hr width="100%">
Komunikaty ze współdzielonego wątku roboczego:<br>
294 Rozdział 12. Komunikacja i wątki

<section id="log"></section>
</body>
</html>

Po załadowaniu strony klienta z listingu 12.7 inicjalizowane jest połączenie ze współdzielonym wątkiem
roboczym, utworzonym na podstawie pliku listing.12.8.js. Ostatnim krokiem jest utworzenie javascriptowego
pliku współdzielonego wątku roboczego. Plik ten, pokazany na listingu 12.8, został utworzony za pomocą
następujących kroków:
1. Utwórz javascriptowy plik o nazwie listing.12.8.js (lub jakiejkolwiek nazwie, którą wybierzesz,
pod warunkiem że odpowiada ona parametrowi pliku wątku współdzielonego na Twojej stronie
klienta HTML-owego).
2. Dodaj zmienne globalne w początkowej części skryptu oraz procedurę obsługi zdarzenia onconnect.
3. Dodaj do skryptu funkcje sendAllConnections i updateAverage.

Listing 12.8. Dodawanie pliku .js współdzielonego wątku roboczego

// 12.8. Plik js współdzielonego przez różne strony wątku roboczego do obliczania średniej liczb

// inicjalizuj tablicę przechowującą porty połączeń


var count = 0;
var connections = new Array();

// inicjalizuj zmienne wzoru na średnią


var average = 0;
var numValues = 0;
var sumValues = 0;

// zdarzenie onconnect współdzielonego wątku roboczego


onconnect = function(msg) {

// pobierz odwołanie do tego połączenia


var port = msg.ports[0];

// zapisz to odwołanie do połączenia dla przyszłych komunikatów


connections[count] = port;

// powiększ liczbę połączeń, które mamy


count += 1;

// odpowiedz klientowi i inicjalizuj jego średnią


port.postMessage({msgType:'LOG',msgText:'[WWR] Teraz połączony [' + count + '].'});
port.postMessage({msgType:'AVE',msgText:'[WWR] Średnia zaktualizowana: ' + average + '.',
´aveValue:average});

// utwórz procedurę obsługi dla przypadku otrzymania komunikatu od klienta


port.onmessage = function(msg) {
// ustaw wartość przekazaną do współdzielonego wątku roboczego
var newValue = msg.data;
// potwierdź otrzymanie wartości
port.postMessage({msgType:'LOG',msgText:'[WWR] Otrzymano: ' + newValue + '.'});
// zastąp średnią nową wartością
updateAverage(newValue);
}
}
Wykorzystywanie współdzielonych wątków roboczych 295

// funkcja pomocnicza do wysyłania wiadomości do wszystkich klientów


function sendAllConnections(msgTypeVal,msgVal) {

// przejdź w pętli przez klienty i wykonaj postMessage


for (var i=0; i<count; i++) {

// wyślij wiadomość do klienta z komunikatem w formacie JSON zawierającym rodzaj wiadomości, tekst i aktualną średnią
connections[i].postMessage({msgType:msgTypeVal,
msgText:msgVal,
aveValue:average});
}
}

// prosta funkcja aktualizująca średnią


function updateAverage(newValue) {

// popraw zmienne wzoru na średnią


numValues++;
sumValues += parseFloat(newValue);

// utwórz nową średnią


average = Math.round((sumValues / numValues)*100)/100;

// aktualizuj we wszystkich klientach nową średnią


sendAllConnections('AVE','[WWR] Średnia zaktualizowana: ' + average + '.');
}

Aby wykonać ten kod, załaduj w przeglądarce stronę pojemnika HTML, która wczyta instancje stron
klientów do trzech ramek iframe, co pokazano na rysunku 12.3. Każda strona klienta utworzy instancję
interfejsu współdzielonego wątku roboczego, co spowoduje utworzenie jednego współdzielonego wątku
roboczego i połączenie z nim strony klienta. Po połączeniu się strony klienta we współdzielonym wątku
roboczym zostanie uruchomiona procedura obsługi zdarzenia onconnect. Procedura obsługi zdarzenia
pobierze odwołanie do portu, z którym to połączenie jest powiązane, tak abyś mógł później wysyłać
wiadomości do tego połączenia klienta, oraz zapisze ten port w tablicy connections. W procedurze obsługi
zdarzenia onconnect współdzielony wątek roboczy wysyła następnie do portu, z którym właśnie się połączył,
komunikat dziennika zawierający informację o podłączeniu się klienta oraz numerze połączenia. Ponadto
w celu inicjalizacji ekranu na jego stronie współdzielony wątek roboczy wysyła do klienta aktualną średnią.
Wreszcie, na potrzeby zdarzenia onmessage jest rejestrowana procedura obsługi zdarzenia dla portu, tak aby po
wysłaniu przez stronę klienta komunikatu do współdzielonego wątku roboczego mógł on zostać przechwycony.
Kiedy współdzielony wątek roboczy wysyła komunikaty dziennika albo ze średnią z powrotem do strony
klienta, procedura obsługi onmessage po stronie klienta przechwyci je. Strona klienta określi na podstawie
wartości pól przekazanego łańcucha JSON rodzaj wiadomości i zaktualizuje zawartość ekranu przez dodanie
wiadomości do dziennika i ustawienie w razie potrzeby wartości średniej.
Gdy wszystkie strony klienta są połączone ze współdzielonym wątkiem roboczym, użytkownik może
wprowadzić dowolną liczbę na którejkolwiek z nich i kliknąć przycisk Wyślij liczbę do współdzielonego wątku
roboczego. Spowoduje to wywołanie w JavaScripcie klienta funkcji sendNumber, co będzie skutkować wysłaniem
zawierającego wprowadzoną liczbę komunikatu do współdzielonego wątku roboczego. Zauważ, że używamy
tutaj atrybutu port obiektu sworker, na którym jest wywoływana funkcja postMessage, co różni się nieznacznie
od przesyłania komunikatów do dedykowanych wątków roboczych:
sworker.port.postMessage(numToSend);
296 Rozdział 12. Komunikacja i wątki

Polecenie wysłania wiadomości wyśle dane wprowadzone przez użytkownika do współdzielonego wątku
roboczego, który następnie pobierze liczbę z zarejestrowanego zdarzenia onmessage i zwróci na port klienta
komunikat dziennika informujący o tym, że liczbę pobrano. Po wysłaniu wiadomości potwierdzającej odbiór
współdzielony wątek roboczy zaktualizuje wartość średniej w zmiennych globalnych i prześle ją do wszystkich
podłączonych klientów. Przekazująca komunikat do wszystkich klientów funkcja pomocnicza przechodzi
w pętli przez tablicę connections i wysyła w komunikacie nową średnią. Rysunek 12.3 przedstawia wyniki
przykładowego uruchomienia przepisu.

Rysunek 12.3. Przykładowe rezultaty pokazujące komunikaty otrzymane przez każdą ze stron klienta

Interfejs SharedWorker należy postrzegać jako młodą technologię, obsługiwaną w niewielu przeglądarkach
i posiadającą niewielką funkcjonalność. Pewne ograniczenia dotyczące tego interfejsu, takie jak brak obsługi
rozłączeń klientów, nie zostały jeszcze przezwyciężone, lecz sam interfejs wydaje się obiecujący i zapewne
w przyszłości znacząco ułatwi programistom pracę.

Wskazówka
Programując własną strukturę wątków roboczych, w celu umożliwienia łatwego debugowania warto
najpierw zaimplementować funkcję wątku na głównej stronie i dopiero wtedy, mając działającą
podstawową funkcjonalność wątku roboczego, umieścić kod w osobnym pliku i wątku.

Podsumowanie
W przepisach zawartych w tym rozdziale pokazano zastosowanie WebSocket API oraz Web Workers API
w niektórych kluczowych wzorcach komunikacji. WebSocket API udostępnia łatwy w użyciu sposób
komunikacji między serwerami i stronami klienta, natomiast wątki robocze — możliwość efektywnej
minimalizacji wpływu intensywnego przetwarzania na główny wątek witryny lub aplikacji. Wykorzystując
te nowe API w swoich aplikacjach, możesz poprawić ogólne działanie i funkcjonalność stron. Przepisy
z tego rozdziału stanowią jedynie zarys możliwości obu API, ale dostarczyły Ci niewątpliwie podstaw do
implementowania swoich własnych rozwiązań.
13
Zachowanie przeglądarek
w HTML5

Z e względów bezpieczeństwa przeglądarki były wcześniej odseparowane od systemu operacyjnego. Wraz


z HTML5 pojawiły się pewne API, które pozwalają na ściślejszą integrację z systemem operacyjnym
używanego przez użytkownika urządzenia i umożliwiają działania typowe dla zwykłych aplikacji. W tym
rozdziale zaprezentujemy trzy spośród interfejsów związanych z zachowaniem przeglądarek. Umożliwiają
one następujące działania:
„ przeciąganie i upuszczanie obiektów poza przeglądarką i na stronie,
„ przechowywanie danych w pamięci podręcznej aplikacji w celu umożliwienia korzystania
ze stron internetowych offline,
„ wyświetlanie na pulpicie powiadomień informujących użytkowników o zdarzeniach z okna przeglądarki.

W tym rozdziale przyjrzymy się tym API, mimo że są one młodsze od innych omawianych w książce
i wciąż wymagają poprawek. Zaczniemy od jednego z bardziej dopracowanych, a zakończymy na API, które
nie jest jeszcze obsługiwane przez większość przeglądarek, ale świetnie rokuje na przyszłość. Przejdźmy zatem
do omawiania Drag and Drop API.

Drag and Drop API


Drag and Drop API zostało dodane do HTML5, aby umożliwić użytkownikom wybranie obiektu, przeciągnięcie
go na jakiś obszar strony i upuszczenie z jednoczesnym uruchomieniem jakiegoś działania. Podstawą
funkcjonalności przeciągania i upuszczania jest połączenie czterech elementów:
„ obiekt, który można przeciągać z przeglądarki albo spoza niej,
„ strefa upuszczania odbierająca obiekt,
„ zdarzenia, które pozwalają sterować zachowaniem przeciągania i upuszczania,
„ magazyn danych do przenoszenia danych kontekstowych dotyczących obiektów.

Przeciąganie obiektu w przeglądarce jest możliwe dzięki pojawieniu się nowego atrybutu o nazwie
draggable. Atrybut ten może przyjmować jedną z trzech wartości: true, false i auto. Jeśli jest ustawiony na
true, obiekt może zostać przeciągnięty przez użytkownika. Jeśli atrybut ma wartość false, obiekt nie może
298 Rozdział 13. Zachowanie przeglądarek w HTML5

być przeciągany. Jeśli wartość jest ustawiona na auto, przeglądarka użyje domyślnej wartości atrybutu dla
danego typu obiektu, tak jakby atrybut draggable nie był ustawiony. Zauważyłeś już zapewne, że możesz
chwycić obrazek na stronie i go przeciągnąć. Oczywiście po upuszczeniu go nic się nie dzieje, ponieważ
strefa upuszczania nie została zdefiniowana; mimo to może on być przeciągany. Kod, za pomocą którego
utworzysz div z atrybutem draggable, powinien wyglądać następująco:
<div class="divClass" id="myDiv" draggable="true">Mój div</div>

Do utworzenia obszaru, na który przeciągany obiekt może być upuszczony, służy dodany w HTML5
atrybut o nazwie dropzone. Atrybut ten może przyjmować jedną z trzech wartości: copy, move albo link.
Wartość, którą wybierzesz, zależy od rodzaju działania, które ma być wykonane po upuszczeniu obiektu.
Przykładowa deklaracja dropzone na elemencie div wygląda następująco:
<div id="divDropzone" dropzone="copy" …

Atrybut dropzone może również wykorzystać filtr, taki jak lista formatów, na przykład f:/image/png
oznacza, że na strefie upuszczania mogą być upuszczone tylko pliki obrazków typu PNG.
Następnie należy obsłużyć rozpoczęcie przeciągania i upuszczania obiektu poprzez przechwycenie
zdarzeń wyzwalanych przez elementy określone wcześniej jako obiekt możliwy do przeciągnięcia oraz strefa
upuszczania. Gdy użytkownik zacznie przeciągać obiekt, zostanie wykonany kod z atrybutu ondragstart
albo zdarzenie dragstart. Dzięki wywołaniu funkcji obsługi tego zdarzenia będzie możliwe zapisanie danych
dotyczących przeciąganego obiektu. Kiedy obiekt zostanie następnie upuszczony w strefie upuszczania,
strefa ta uruchomi kod atrybutu o nazwie ondrop albo zdarzenie drop. Po wywołaniu tego zdarzenia skrypt
może pobrać dane zapisane w przeciąganym obiekcie i użyć ich do wykonania dowolnie wybranej funkcji.
Do przekazania tej informacji wykorzystujemy nowy interfejs o nazwie DataTransfer.
Interfejs DataTransfer posiada różne atrybuty i metody. Oto dostępne metody:
„ setData(format, dane) — ustawia dane typu format (na przykład text) w pamięci danych.
„ getData(format) — pobiera dane typu format z pamięci danych.
„ clearData([format]) — czyści pamięć danych z danych wskazanego opcjonalnie formatu;
jeśli format nie zostanie podany, czyszczona jest cała pamięć.
„ void setDragImage(obraz, x, y) — określa obraz wyświetlany podczas przeciągania oraz
położony w jego obrębie punkt, na który powinien wskazywać kursor.
„ void addElement(element) — dodaje przekazany element do pamięci danych.

Do przekazania informacji są wykorzystywane dwie metody: setData i getData.


Tabela 13.1 podsumowuje obsługę Drag and Drop API w najpopularniejszych przeglądarkach.

Tabela 13.1. Dostępność API przeciągania i upuszczania w przeglądarkach

Android 2.1+
Chrome 10.0+
Firefox 3.6+
Internet Explorer 6.0+
iOS Safari -
Opera -
Safari 3.2+
Przeciąganie i upuszczanie pomiędzy elementami div 299

PRZEPIS DLA POCZĄTKUJĄCYCH


Przeciąganie i upuszczanie pomiędzy elementami div
W tym przepisie funkcjonalność przeciągania i upuszczania zostanie wykorzystana do umożliwienia
użytkownikowi przeciągania obrazka notatki do strefy upuszczania (dropzone) na korkowej tablicy.
Po upuszczeniu notatki do tablicy zostanie dodana nowa kopia wybranego obrazka notatki, co pokazano
na rysunku 13.1.

Rysunek 13.1. Przykładowy wynik obrazujący prostą funkcjonalność przeciągania i upuszczania przy użyciu
znajdujących się z lewej strony notatek, które można przeciągać, oraz strefy upuszczania z prawej

W tym przepisie przedstawimy podstawy realizacji funkcjonalności przeciągania i upuszczania za pomocą


atrybutów draggable i dropzone oraz kluczowe zdarzenia i interfejs DataTransfer. Listing 13.1 pokazuje kod
tego przykładu. W celu utworzenia pliku wykonaj następujące kroki:
1. Utwórz pustą stronę HTML z elementami style i body pokazanymi na listingu 13.1. W elemencie
body dodaj do obrazków notatek oraz elementu div tablicy atrybuty procedury obsługi zdarzeń.
2. Dodaj funkcje dragStartHandler i dropHandler, pobierające element, na którym zostało aktywowane
zdarzenie, oraz samo zdarzenie.
3. Dodaj funkcje getStartPos i init, które — odpowiednio — pobierają pozycję początkową
przeciąganego elementu i inicjalizują styl.

Listing 13.1. Prosty przykład przeciągania i upuszczania

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>13.1. Proste przeciąganie i upuszczanie</title>
<style>
#notes {
float:left;
height:220px;
width:50px;
text-align:center;
border-style:solid;
border-width:2px;
border-color:#333;
background-color:#666;
}
#board {
300 Rozdział 13. Zachowanie przeglądarek w HTML5

float:left;
width:300px;
height:220px;
border-style:solid;
border-width:2px;
border-color:#333;
background-image:url('corkboard.png');
}
</style>
<script>

// zmienne przechowujące pozycję kliknięcia na obrazku


var imgOffsetX = 0;
var imgOffsetY = 0;

// funkcja obsługująca rozpoczęcie przeciągania


function dragStartHandler(tgt, evt) {
evt.dataTransfer.setData("Text", tgt.id);
}

// obsługa upuszczania
function dropHandler(tgt, evt) {

// zapobiegnij wystąpieniu domyślnego działania przeglądarki


// na przykład otwarciu nowej zakładki z przeciąganym elementem
if(evt.preventDefault) {
evt.preventDefault();
}

// pobierz identyfikator przeciąganego obrazka


var elImgId = evt.dataTransfer.getData("Text");

// utwórz nową instancję obrazka


var oImg = new Image();

// ustaw src obrazka


oImg.src = document.getElementById(elImgId).src;

// ustaw położenie obrazka


oImg.style.position='absolute';
var newX = evt.x - imgOffsetX;
var newY = evt.y - imgOffsetY;

var divLeft = tgt.x;


var divTop = tgt.y;
var divWidth = parseInt(tgt.style.width);
var divHeight = parseInt(tgt.style.height);

// upewnij się, że cały obrazek znajduje się wewnątrz elementu div


var imgWH = 50;
if (newX<tgt.offsetLeft) {
newX=tgt.offsetLeft
} else if (newX+imgWH>(tgt.offsetLeft + divWidth)) {
newX = tgt.offsetLeft + divWidth-imgWH;
}
if (newY<tgt.offsetTop) {
newY=tgt.offsetTop;
} else if (newY+imgWH>(tgt.offsetTop + divHeight)) {
newY = tgt.offsetTop + divHeight-imgWH;
}
Przeciąganie i upuszczanie pomiędzy elementami div 301

// spozycjonuj obrazek
oImg.style.left = newX+'px';
oImg.style.top = newY+'px';

// dodaj obrazek do strefy upuszczania elementu div


tgt.appendChild(oImg);
}

// funkcja pomocnicza do ustawiania punktu kliknięcia obrazka notatki


function getStartPos(evt) {
imgOffsetX = evt.offsetX;
imgOffsetY = evt.offsetY;
}

// inicjalizuj szerokość i wysokość tablicy


function init() {
document.getElementById('board').style.width = '300px';
document.getElementById('board').style.height = '220px';
}

// wywołaj funkcję init po załadowaniu strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Przeciągnij notatki na tablicę korkową:</h1>
<div id="notes">
<img id="note_blue" src="post_blue.png" onmousedown="getStartPos(event);"
´ondragstart="dragStartHandler(this, event);"><br>
<img id="note_green" src="post_green.png" onmousedown="getStartPos(event);"
´ondragstart="dragStartHandler(this, event);"><br>
<img id="note_pink" src="post_pink.png" onmousedown="getStartPos(event);"
´ondragstart="dragStartHandler(this, event);"><br>
<img id="note_yellow" src="post_yellow.png" onmousedown="getStartPos(event);"
´ondragstart="dragStartHandler(this, event);">
</div>
<div id="board" dropzone="copy" ondrop="dropHandler(this, event);" ondragover="return false;"></div>
</body>
</html>

Po załadowaniu strony w przeglądarce każdy obrazek powoduje ustawienie w procedurze obsługi


zdarzenia ondragstart wywołania dragStartHandler z odpowiednim elementem. Zauważ, że nie trzeba
dodawać atrybutu draggable, gdyż dla obrazków jest to domyślne ustawienie. W wierszu dotyczącym
elementu div tablicy ustawiamy atrybut dropzone oraz definiujemy procedury obsługi zdarzeń ondrop
i ondragover. Procedura obsługi zdarzenia ondragover zastępuje standardową funkcjonalność przeglądarki
i po prostu zwraca false, gdy zdarzenie to jest wywoływane. Jeśli użytkownik wybierze notatkę i rozpocznie
jej przeciąganie, zostanie wywołana funkcja dragStartHandler. Zdarzenie onmousedown uruchomi ponadto
funkcję pomocniczą, która zapisuje współrzędne x i y w zmiennych używanych przy obliczaniu miejsca
umieszczenia notatki na tablicy. W funkcji dragStartHandler skrypt ustawia identyfikator przeciąganego
elementu w interfejsie DataTransfer.
Użytkownik upuszcza następnie notatkę na tablicy, co powoduje wywołanie funkcji dropHandler.
Pierwszym realizowanym w procedurze obsługi upuszczania zadaniem jest zablokowanie domyślnego
zachowania przeglądarki przez wywołanie na zdarzeniu metody preventDefault, co pozwala sterować
302 Rozdział 13. Zachowanie przeglądarek w HTML5

zachowaniem działania upuszczania w przeglądarce. Skrypt otrzymuje następnie z pamięci danych


identyfikator elementu, tworzy nowy obrazek, ustawia jego źródło oraz wykonuje obliczenia, aby ustalić
położenie nowego obrazka na tablicy. Zauważ, że zależnie od obsługiwanych sposobów pozycjonowania
będzie ono działać tylko w niektórych przeglądarkach, na przykład w Chrome, ale nie jest to istotne dla
przykładu. Kiedy obrazek jest już utworzony i spozycjonowany, dodajemy go do elementu div tablicy,
co skutkuje utworzeniem kopii obrazka notatki.
Jest to prosty przykład funkcjonalności przeciągania i upuszczania. W kolejnym przepisie połączymy
przedstawione tu koncepcje z innymi zdarzeniami przeciągania i upuszczania, aby uzyskać znacznie większą
kontrolę nad tą funkcjonalnością.

PRZEPIS DLA ZAAWANSOWANYCH


Wykorzystanie zdarzeń i obiektu dataTransfer
W poprzednim przepisie użyliśmy jedynie zasadniczych elementów Drag and Drop API w celu przechwycenia
upuszczanego nad pewnym obszarem obrazka i dodania nowego obrazka do tego obszaru. Interfejs API
oprócz podstawowych zdarzeń przeciągania i upuszczania udostępnia kilka innych, za pomocą których
można sterować takimi czynnikami jak rodzaj elementów, które mogą być przeciągane, oraz miejsce, gdzie
mogą zostać upuszczone. Oto pozostałe zdarzenia, które generuje Drag and Drop API:
„ dragstart — operacja przeciągania obiektu została rozpoczęta,
„ drag — obiekt jest przesuwany,
„ dragenter — operacja przeciągania wkracza do dostępnej strefy upuszczania,
„ dragleave — operacja przeciągania opuszcza dostępną strefę upuszczania,
„ dragover — obiekt jest przeciągany nad dostępną strefą upuszczania,
„ drop — obiekt został zwolniony nad dostępną strefą upuszczania,
„ dragend — operacja przeciągania obiektu zakończyła się.

W tym przepisie wykorzystamy Drag and Drop API do utworzenia matematycznej gry znanej jako
Wieże Hanoi, opracowanej przez matematyka Édouarda Lucasa w 1883 roku (więcej na temat historii gry
w Wikipedii, pod adresem http://pl.wikipedia.org/wiki/Wieże_Hanoi). Gracz ma do dyspozycji trzy słupki,
z których ten znajdujący się po lewej stronie zawiera początkowo szereg krążków (ich liczba może się
różnić w zależności od wersji gry), ułożonych od największego (na dole) do najmniejszego. Celem gry jest
przeniesienie stosu na słupek znajdujący się po prawej stronie, a pojedynczy ruch polega na przesunięciu
jednego i tylko jednego krążka albo na pusty słupek, albo na słupek zawierający krążek większy od przesuwanego.
Do utworzenia gry z zachowaniem jej reguł użyjemy zdarzeń Drag and Drop API i obiektu dataTransfer.
Jeśli podczas gry gracz spróbuje umieścić krążek na stosie, na którym znajduje się już mniejszy krążek,
program nie pozwoli go upuścić. Jeśli jednak ruch jest prawidłowy, krążek zostanie przesunięty i dodany
do stosu. Jeżeli użytkownik spróbuje przesunąć krążek, który nie znajduje się na szczycie danego stosu, nie
będzie mógł tego zrobić. Po każdym przesunięciu krążka na stos znajdujący się skrajnie po prawej stronie
program sprawdzi, czy użytkownik przesunął już cały stos — a jeśli tak, pojawią się gratulacje. Sprawdźmy,
w jaki sposób wykorzystać różne zdarzenia do egzekwowania reguł gry:
Wykorzystanie zdarzeń i obiektu dataTransfer 303

1. Utwórz stronę pokazaną na listingu 13.2, zawierającą znaczniki style oraz znaczniki krążków
i trzech stref upuszczania. Zauważ, że przy krążkach jest dodawany atrybut draggable, tak aby
przeglądarka wiedziała, że odpowiednie elementy div mogą być przeciągane.
2. Dodaj znaczniki script, funkcję initTowers oraz window.addEventListener do uruchamiania
inicjalizacji.
3. Dodaj do krążków procedury obsługi zdarzeń. Funkcje blockHandleDragStart oraz
blockHandleDragEnd pozwolą na przeciąganie tylko górnych krążków wież. Te funkcje zmienią
również styl na początku i końcu procesu przeciągania.
4. Oprócz obsługi krążków potrzebujemy obsługi strefy upuszczania. Dodaj funkcje obsługi stref
upuszczania wież, towerHandleDragOver, towerHandleDragLeave i towerHandleDragDrop, aby móc
zmieniać styl stref podczas przesuwania się nad nimi krążka oraz obsługiwać upuszczanie krążka
na wieżę.
5. Na początku skryptu dodaj deklaracje zmiennych globalnych przechowujących mapę krążków
należących do wież w grze (dwuwymiarową tablicę o nazwie towers), liczbę krążków w grze oraz
licznik ruchów wykonanych przez gracza.

Uwaga
Obrazki do tego przepisu możesz pobrać razem z listingami kodu (informacje o nich znajdziesz
we wstępie).

Listing 13.2. Funkcjonalność przeciągania i upuszczania w grze Wieże Hanoi

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>13.2. Gra w Wieże Hanoi</title>
<style>
.towerDropZone {
float: left;
height: 200px;
width: 200px;
margin: 5px;
padding: 15px;
position:relative;
background-color:#fff;
background-image:url(tower.png);
background-repeat:no-repeat;
background-position:bottom;
}
.towerDropZone.over {
border-radius: 15px 15px;
background-color:#EEE;
}
.tower {
width: 200px;
position:absolute;
bottom:1px;
border:none;
}
.block {
height: 25px;
304 Rozdział 13. Zachowanie przeglądarek w HTML5

margin:1px auto;
border: 1px solid #ccc;
border-radius: 15px 15px;
background-color:#00F;
color:#FFF;
text-align:center;
font-size:19px;
font-weight:bold;
}
</style>
<script>
// zadeklaruj mapę wież, liczbę krążków i licznik kroków
var towers = [[],[],[]];
var numblocks = 4;
var numMoves = 0;

// inicjalizuj procedury obsługi zdarzeń oraz stan


function initTowers() {

// ustaw procedury obsługi zdarzeń dla stref upuszczania wież


var towerDropZones = document.querySelectorAll('#towers .towerDropZone');
[].forEach.call(towerDropZones, function(tdz) {
tdz.addEventListener('dragover', towerHandleDragOver, false);
tdz.addEventListener('drop', towerHandleDragDrop, false);
tdz.addEventListener('dragleave', towerHandleDragLeave, false);
});

// ustaw procedury obsługi zdarzeń dla poszczególnych krążków


var blocks = document.querySelectorAll('.block');
[].forEach.call(blocks, function(block) {
block.addEventListener('dragstart', blockHandleDragStart, false);
block.addEventListener('dragend', blockHandleDragEnd, false);
});

// skonfiguruj mapę początkowego stanu krążków na wieży numer 0


for (var i=numblocks-1;i>=0;i--) {

// dodaj do mapy wież krążek na pierwszej wieży (3, 2, 1, 0)


towers[0].push(i);

// utwórz krążki o różnych szerokościach


document.getElementById(i+"block").style.width = (90 + i * 30) + "px";
}
}

// obsługa przeciągania krążka nad strefą upuszczania wieży


function towerHandleDragOver(e) {
// zapobiegnij wystąpieniu domyślnego działania, tak aby możliwe było upuszczenie krążka
if (e.preventDefault) {
e.preventDefault();
}

// ustaw 'move' jako skutek upuszczenia


e.dataTransfer.dropEffect = 'move';

// ustaw wygląd wieży, aby wyglądała na dostępną


this.className = "towerDropZone over";

return false;
}
Wykorzystanie zdarzeń i obiektu dataTransfer 305

// obsługa opuszczania strefy upuszczania wieży przez przeciągany krążek


function towerHandleDragLeave(e) {
// ustaw wygląd strefy upuszczania wieży z powrotem na normalny
this.className = "towerDropZone";
}

// obsługa upuszczania krążka na strefę upuszczania wieży


function towerHandleDragDrop(e) {

// zapobiegnij wystąpieniu domyślnego działania


if (e.preventDefault) {
e.preventDefault(); // konieczne; umożliwia nam upuszczanie
}

// zmień wygląd strefy upuszczania wieży z powrotem na normalny


this.className = "towerDropZone";

// pobierz dane JSON przekazane poprzez obiekt dataTransfer


var blockInfo = JSON.parse(e.dataTransfer.getData("Text"));

// ustaw informacje o krążku oraz wieżach, z której i na którą jest on przesuwany


var blockId = blockInfo.blockId;
var blockNum = parseInt(blockInfo.blockId);
var fromTowerId = parseInt(blockInfo.fromTowerId);
var toTowerId = this.id;

// pobierz element reprezentujący wieżę


var tower = document.getElementById("tower"+toTowerId);

// algorytm określania, czy krążek może być upuszczony na wieżę


var towerheight = towers[toTowerId].length;
if ( towerheight == 0) {
// wieża jest pusta — wstaw krążek przed znacznikiem <p> w wieży
tower.insertBefore(document.getElementById(blockId),document.getElementById("p"+toTowerId));
} else {
// wieża zawiera krążki — pobierz wartość górnego krążka wieży
var topBlock = towers[toTowerId][towerheight-1];

// sprawdź, czy na górnym krążku może być umieszczony kolejny


if ( topBlock > blockNum) {
// wstaw krążek przed górnym krążkiem, aby teraz on był na górze
tower.insertBefore(document.getElementById(blockId),document.getElementById(topBlock+"block"));
} else {
// krążek nie może być umieszczony na krążku będącym aktualnie na górze
// zwróć false
return false;
}
}

// aktualizuj pozycję krążka na mapie gry


towers[toTowerId].push(blockNum);
towers[fromTowerId].pop();

// zwiększ liczbę wykonanych ruchów


numMoves++;
document.getElementById("numMoves").textContent = numMoves;

// sprawdź, czy gra się zakończyła — czy wszystkie krążki znajdują się w dobrej kolejności na trzeciej wieży
if (towers[2].length==numblocks) {
306 Rozdział 13. Zachowanie przeglądarek w HTML5

// gra zakończona — zablokuj możliwość przeciągania krążków


var blocks = document.querySelectorAll('.block');
[].forEach.call(blocks, function(block) {
block.draggable = false;
});

// powiadom użytkownika
alert("Gratulacje — przesunąłeś wieżę.");
}
}

// obsługa rozpoczęcia przeciągania krążka


function blockHandleDragStart(e) {

// pobierz identyfikator krążka oraz identyfikator wieży, na której się on początkowo znajduje
var blockId = this.id;
var fromTowerId = this.parentNode.parentNode.id;

// ustaw wartości zmiennych, żeby móc sprawdzić, czy uchwycony krążek znajduje się na górze wieży
var towerheight = towers[fromTowerId].length;
var topBlock = towers[fromTowerId][towerheight-1];
var thisBlock = parseInt(blockId);

// sprawdź, czy jest to górny krążek na wieży


if (topBlock == thisBlock) {

// krążek znajduje się na górze, więc możemy go przesunąć


this.style.opacity = '0.4';

// utwórz paczkę JSON do przekazania informacji za pomocą obiektu dataTransfer


var blockinfo = {
"blockId": blockId,
"fromTowerId": fromTowerId};

// ustaw dane obiektu dataTransfer


e.dataTransfer.setData("Text", JSON.stringify(blockinfo));

} else {

// krążek nie znajduje się na górze


this.style.opacity = '1.0';

// zwróć false
return false;
}
}

// obsługa zakończenia przeciągania krążka


function blockHandleDragEnd(e) {

// ustaw z powrotem normalną przezroczystość krążka


this.style.opacity = '1.0';
}

// po załadowaniu strony wywołaj funkcję inicjalizującą


window.addEventListener('load',initTowers,false);

</script>
</head>
<body>
Wykorzystanie zdarzeń i obiektu dataTransfer 307

<h1>Gra w Wieże Hanoi</h1>


<p>Przesuń krążki z lewego stosu na wieżę znajdującą się po prawej stronie.<br>
Krążek może być przesunięty jedynie na pustą wieżę albo na wieżę zawierającą większy krążek.</p>
<div>Liczba wykonanych ruchów: <span id="numMoves">0</span></div>
<section id="towers">
<div class="towerDropZone" id="0">
<div class="tower" id="tower0">
<div class="block" id="0block" draggable="true">1</div>
<div class="block" id="1block" draggable="true">2</div>
<div class="block" id="2block" draggable="true">3</div>
<div class="block" id="3block" draggable="true">4</div>
<p id="p0" />
</div>
</div>
<div class="towerDropZone" id="1">
<div class="tower" id="tower1"><p id="p1" /></div>
</div>
<div class="towerDropZone" id="2">
<div class="tower" id="tower2"><p id="p2" /></div>
</div>
</section>
</body>
</html>

Teraz, kiedy zbudowałeś już stronę albo nawet zagrałeś kilka rund, przeciągając krążki w tę i z powrotem,
masz wyobrażenie tego, w jaki sposób działania gracza aktywują zdarzenia. Poświęćmy jednak kilka minut,
by omówić pewne szczegóły gry związane z funkcjonalnością przeciągania i upuszczania.
Po załadowaniu strony deklarowane są zmienne globalne: mapa gry (jeśli projektowałeś gry, w których
należy w jakiś sposób rejestrować położenie — gracza, figury itp. — mapa gry będzie Twoją drugą naturą;
w przeciwnym razie traktuj mapę jako reprezentację informacji o położeniu pionków gry w określonym
momencie), liczba krążków w grze oraz licznik ruchów gracza. Mapa gry jest w naszym przypadku
dwuwymiarową tablicą przechowującą informacje o tym, które krążki znajdują się na każdej z trzech wież.
Strefy upuszczania oraz wieże mają identyfikatory w postaci liczb całkowitych rozpoczynających się od zera
— identyfikatory te odpowiadają indeksom tablicy towers. Po uruchomieniu funkcji initTowers strona
rejestruje procedury obsługi strefy upuszczania dla każdej strefy upuszczania wieży, a następnie procedury
obsługi zdarzeń dla krążków, które będą przesuwane. Skrypt ustawia następnie aktualną mapę gry, przypisując
w tablicy wieży o numerze zero (pierwsza wieża) cztery krążki: 3, 2, 1, 0. Zauważ, iż dodawane są one
w odwróconej kolejności, co odzwierciedla fakt, że trzeci krążek jest większy od drugiego i tak dalej.
Kolejność ta jest dodatkowo wykorzystywana do dynamicznego ustawienia rozmiaru krążków poprzez
nadanie odpowiedniego stylu. Na tym kończy się inicjalizacja planszy gry.
Przyjrzyjmy się teraz przebiegowi gry. Kiedy użytkownik przeciąga krążek z jednej wieży na inną, w miarę
generowania zdarzeń dzieje się kilka rzeczy. Najpierw generowane jest zdarzenie rozpoczęcia przeciągania
i uruchamiana jest funkcja blockHandleDragStart. Funkcja ta sprawdza, czy wybrany krążek jest pierwszym
od góry krążkiem swojej wieży. Jeśli nie, funkcja zwraca false. Jeśli jest on górnym krążkiem, zostaje
utworzony łańcuch JSON zawierający pewne kluczowe informacje: identyfikator krążka oraz wieży, z której
jest on przeciągany. Dane te są następnie zapamiętywane za pomocą interfejsu DataTransfer, tak aby
informacja o wybranym krążku i wieży, z której jest przenoszony, była dostępna, kiedy krążek ten zostanie
upuszczony na nową wieżę. Aby pokazać użytkownikowi, że określony krążek jest przeciągany, w funkcji
zmieniana jest także przezroczystość krążka.
308 Rozdział 13. Zachowanie przeglądarek w HTML5

Kiedy krążek jest następnie przeciągany nad strefę upuszczania albo wieżę, generowane jest zdarzenie
dragover i uruchamiana funkcja towerHandleDragOver. Zadaniem tej funkcji jest powiadomienie gracza,
że krążek znalazł się nad strefą upuszczania. Jest to realizowane poprzez zmianę stylu obszaru div wieży,
nad którą przeciągany jest krążek, co pokazano na rysunku 13.2.

Rysunek 13 2. Przykładowy zrzut ekranu pokazujący trzeci krążek przeciągany z pierwszej do ostatniej wieży.
Zdarzenie dragstart było wygenerowane już wcześniej, a zdarzenie dragover zostało wygenerowane
po przeciągnięciu krążka nad strefę upuszczania

Kluczową sprawą jest jednak uwzględnienie polecenia preventDefault. Jak dowiedziałeś się z poprzedniego
przepisu, polecenia tego należy użyć, aby zapobiec wykonaniu przez przeglądarkę domyślnego działania,
które może spowodować problemy związane z przebiegiem gry.
Jeśli krążek jedynie „przechodzi” nad strefą upuszczania wieży, zostanie wygenerowane zdarzenie
dragleave i wykonana funkcja towerHandleDragLeave. Funkcja ta zmienia styl opuszczanej strefy upuszczania
wieży z powrotem na normalny.
Kiedy krążek zostaje upuszczony na strefę upuszczania wieży, wykonywane jest zasadnicze posunięcie
zawarte w funkcji towerHandleDragDrop. Po uruchomieniu tej funkcji najpierw zostaje wywołana metoda
preventDefault, która ponownie ma za zadanie zapobiec wykonaniu przez przeglądarkę domyślnego
działania. Następnie z obiektu dataTransfer są pobierane informacje, które zostaną wykorzystane przez
algorytm gry. Kluczem jest tutaj określenie, czy krążek może być upuszczony, co odbywa się poprzez
sprawdzenie na mapie gry, czy wieża, na którą krążek jest upuszczany, jest pusta lub czy znajduje się już na
niej większy krążek. Jeśli wieża zawiera mniejszy krążek, przesuwany krążek nie może być do niej dodany,
a jako rezultat funkcji zwracana jest wartość false. Jeśli krążek może być dodany, zostaje „wstawiony” albo
przed górnym krążkiem (po to, aby wyświetlać krążki prawidłowo), albo przed znacznikiem znajdującym
się na wieży, którym jest znacznik akapitu (<p id="px">). Po dodaniu krążka kod aktualizuje mapę gry,
zwiększa licznik ruchów i na końcu sprawdza, czy gracz ukończył grę.
W celu sprawdzenia, czy gracz ukończył grę, wykonywany jest bardzo prosty test — sprawdzenie, czy
ostatnia wieża zawiera wszystkie cztery krążki. Ponieważ reguły gry zapobiegają umieszczeniu na wieży
mniejszego krążka poniżej większego, nie ma potrzeby weryfikowania kolejności krążków. Kiedy zadanie jest
rozwiązane, gracz nie powinien mieć możliwości dalszego przesuwania krążków, co realizujemy poprzez
ustawienie atrybutu draggable każdego krążka na false. To kończy grę — aby zagrać ponownie, gracz musi
jeszcze raz załadować stronę.
Grę można znacznie ulepszyć na przykład poprzez zmianę koloru strefy zrzutu na podstawie informacji,
czy krążek może być upuszczony na jakimś obszarze. Teraz, gdy rozumiesz już przepływ zdarzeń w Drag
and Drop API oraz wiesz, jak dodawać reguły postępowania zależne od zdarzeń, pozostawiamy Ci dodanie
podobnych funkcji do samodzielnego wykonania, o ile tylko masz na to ochotę.
Pamięć podręczna aplikacji i jej API 309

W obu przykładach dotyczących Drag and Drop API za pomocą interfejsu DataTransfer przekazywaliśmy
proste ciągi tekstowe. Jak wspomniano wcześniej, interfejs DataTransfer może obsługiwać wiele rodzajów
przenoszonej informacji, w tym obrazki, pliki itp. Przykłady obejmują jedynie podstawy uzyskiwania
poprawnego rozwiązania wykorzystującego przeciąganie i upuszczanie. Istnieje wiele innych funkcji Drag
and Drop API, np. inne zachowania, przeciągane obiekty oraz przeciąganie z pulpitu do przeglądarki.
W rozdziale 14. dowiesz się, w jaki sposób przy użyciu pewnych szczególnych funkcji interfejsu DataTransfer
realizowane jest przeciąganie i upuszczanie plików spoza przeglądarki do strefy upuszczania na stronie.

Wskazówka
Przy debugowaniu swojego programu wykorzystującego przeciąganie i upuszczanie możesz czasem
napotkać pewne trudności. Jeśli za każdym razem, kiedy upuszczasz obiekt na swojej strefie
upuszczania, przeglądarka uruchamia nową kartę albo okno zawierające jedynie obiekt, który
przeciągasz, oznacza to najprawdopodobniej, że opuściłeś wywołanie na generowanym zdarzeniu
metody preventDefault. Bez wywołania tej metody przeglądarki wykonują swoje domyślne procesy
obsługi upuszczania w oparciu o rodzaj przeciąganego obiektu. Na przykład w Firefoksie przy próbie
przeciągnięcia i upuszczenia obrazka na strefę upuszczania, która nie wykonuje metody preventDefault,
zostanie otwarta nowa karta pokazująca jedynie przeciągany obrazek.

Pamięć podręczna aplikacji i jej API


Jeśli chcesz, żeby aplikacja była zawsze dostępna na smartfonach i tabletach, potrzebujesz aplikacji
natywnej. Jeśli chcesz korzystać z aplikacji przez stronę internetową, potrzebujesz połączenia z internetem
— a przynajmniej potrzebowałbyś, gdyby nie była dostępna pamięć podręczna aplikacji. Pamięć podręczna
aplikacji pozwala na uruchamianie aplikacji internetowych offline nie tylko na smartfonach lub tabletach,
ale także na zwykłych przeglądarkach.
Tabela 13.2 ilustruje wersje najpopularniejszych przeglądarek, które obsługują pamięć podręczną aplikacji
i jej API.

Tabela 13 2. Obsługa pamięci podręcznej aplikacji w przeglądarkach

Android 2.1+
Chrome 10.0+
Firefox 3.6+
Internet Explorer -
iOS Safari 3.2+
Opera 10.6+
Safari 4.0+

Pamięć podręczna aplikacji to lista plików przechowywanych w pliku manifestu, które przeglądarka
powinna pobrać i zapisać, tak aby kiedy użytkownik powróci na stronę, pliki te były gotowe do wykorzystania.
Jest to niezwykle przydatne, jeśli nie masz połączenia z siecią, lecz nawet jeśli je masz, przyspiesza czas
wczytywania strony, ponieważ odpowiednie pliki zostały już załadowane.
Każda strona, która odwołuje się do manifestu, będzie automatycznie zapisywana, ale nie oznacza to,
że powinieneś przechowywać wszystko offline (weź pod uwagę ograniczenia związane z rozmiarem i typem
pliku). Powinieneś dbać o odpowiednie z punktu widzenia użytkownika działanie aplikacji. Offline powinny
być zapisywane kluczowe pliki aplikacji.
310 Rozdział 13. Zachowanie przeglądarek w HTML5

Pamięć podręczna przeglądarki a bezpieczeństwo


Pamięć podręczna aplikacji zapisuje wybrane przez programistę pliki w systemie plików użytkownika,
co może stwarzać pewne zagrożenie dla witryn i aplikacji. Na przykład na komputerze klienta mógłby zostać
zapisany złośliwy plik z serwera. Mając to na uwadze, przed zapisaniem jakichkolwiek plików przeglądarka
zapyta użytkownika o to, czy ufa on witrynie w kwestii lokalnego zapisywania danych i plików. Rysunek 13.3
przedstawia przykład zapytania użytkownika o zgodę.

Rysunek 13.3. Pytanie o zezwolenie na zapisanie danych aplikacji w pamięci podręcznej

Odwołania do pliku manifestu


Przeglądarka może w łatwy sposób odwołać się do pliku manifestu. Wystarczy dodać do elementu html
atrybut manifest zawierający nazwę pliku manifestu (.appcache), tak jak poniżej:
<!DOCTYPE html>
<html manifest="application.appcache">
<head>
</head>
<body>
</body>
</html>

Uwaga
Każdy plik (.html, .php itp.), do którego odwołanie znajduje się w pliku manifestu, zostanie pobrany
i zapisany w pamięci podręcznej.

Twój serwer może nie wiedzieć, co zrobić z plikiem .appcache, więc prawdopodobnie będziesz musiał
dodać do jego konfiguracji nowy typ MIME. Możesz tak zrobić w przypadku IIS, a jeśli używasz serwera
Apache, wykorzystaj następujący kod:
AddType text/cache-manifest .appcache

PRZEPIS DLA POCZĄTKUJĄCYCH


Tworzenie pliku manifestu
Do utworzenia pliku manifestu nie potrzebujesz żadnego specjalnego oprogramowania. Możesz użyć
dowolnego, prostego edytora tekstu, takiego jak Notatnik. Zapisz plik jako xxx.appcache. Na początku
pliku musi się znajdować następujący wiersz:
CACHE MANIFEST

Uwaga
Nie polecamy tworzenia pliku manifestu przed zakończeniem pracy nad aplikacją lub witryną
— w przeciwnym razie w pamięci podręcznej buforowane będą nieukończone pliki, które mogą
zakłócać tworzenie tej aplikacji lub witryny. Czyszczenie albo odświeżanie pamięci podręcznej
nie jest tak proste jak odświeżanie strony (omówimy to w dalszej części tego rozdziału).
Tworzenie pliku manifestu 311

Następnie możesz po prostu zacząć wymieniać pliki, które mają być zapisywane, jak pokazano poniżej:
CACHE MANIFEST
css/main.css
js/jquery.js
images/logo.png

I to w zasadzie wszystko. Jeśli utworzyłeś stronę HTML i dołączyłeś plik manifestu z listą plików,
po odwiedzeniu strony online zostanie ona zapisana wraz z dodatkowymi plikami w pamięci podręcznej.
Jeśli następnie zamkniesz połączenie internetowe i odświeżysz stronę, wyświetli się ona jak zwykle. Pliki
wymienione w manifeście mogą mieć ścieżki względne albo bezwzględne.
Możesz jednak zrobić więcej. Możesz podzielić plik manifestu na trzy części, tj. CACHE, FALLBACK oraz
NETWORK:
„ CACHE — lista plików, które należy przechowywać w urządzeniu przeglądarki klienta w celu ich
wykorzystania, gdy połączenie internetowe jest niedostępne.
„ FALLBACK — lista plików i ich zamienników, na wypadek gdyby połączenie z internetem nie było
dostępne.
„ NETWORK — pliki, które wymagają połączenia i nie powinny być buforowane.

CACHE
Fragment z nagłówkiem CACHE jest zasadniczo taki sam jak w poprzednim przykładzie. Pliki wymienione tak
jak powyżej albo w części z nagłówkiem CACHE są „jawnie wyspecyfikowane” i zostaną pobrane i zbuforowane
w przeglądarce. Podczas buforowania plików nie możesz używać symboli wieloznacznych, więc maska css/*
nie zadziała.

Ostrzeżenie
Samego manifestu nie zapisuj w pamięci podręcznej, ponieważ poinformowanie przeglądarki o nowym
manifeście stanie się wówczas prawie niemożliwe.

FALLBACK
Część manifestu mająca nagłówek FALLBACK może być wykorzystana do wykrywania, czy użytkownik jest
online, czy offline. Za pomocą sekcji FALLBACK można poinstruować przeglądarkę, którego pliku użyć, jeśli
określony plik jest niedostępny, ponieważ użytkownik pracuje offline. Na przykład duży plik CSS, który
nie jest konieczny do pracy offline, można pominąć w części CACHE i zamiast tego wykorzystać FALLBACK,
jak pokazano poniżej:
CACHE MANIFEST
FALLBACK:
online.css offline.css

W tym przykładzie plik manifestu mówi przeglądarce, aby w przypadku gdy strona HTML odwołuje się
do pliku online.css, a użytkownik pracuje offline, użyła pliku offline.css (zauważ, że nazwy plików są oddzielone
spacją), który został zbuforowany. Przydatne, prawda? Ten sposób nie jest ograniczony jedynie do plików
CSS, lecz może być także użyty w przypadku obrazków, plików javascriptowych, stron HTML, a nawet filmów.
Wystarczy umieścić każdą deklarację w nowym wierszu.
312 Rozdział 13. Zachowanie przeglądarek w HTML5

Używając nagłówka FALLBACK, możesz wykorzystać symbole wieloznaczne. Gdybyś chciał, aby wszystkie
pliki miały określony plik zastępczy, mógłbyś wykorzystać następujący zapis:
/ /offline.html

Jeśli zatem użytkownik pracuje offline, zamiast każdego pliku, którego nazwa znajduje się po ukośniku
(np. /page1.html), zostanie wyświetlony plik offline.html. Pamiętaj jednak, że po otwarciu dowolnego pliku,
do którego odwołanie znajduje się w pliku manifestu, plik ten zostanie pobrany i będzie dostępny offline.
Można pójść dalej i wskazać w sekcji FALLBACK zastępcze pliki dla zawartości określonych katalogów
lub po prostu dla wszystkich stron HTML:
/js/ /offline.js
*.html /offline.html

NETWORK
Fragment z nagłówkiem NETWORK mówi przeglądarce, które pliki mają być dostępne tylko przy aktywnym
połączeniu, i zapobiega pobieraniu tych plików. Zasoby znajdujące się w tej części wymagają zwykle połączenia
z bazą danych albo serwerem — przykładem może być strona logowania:
CACHE MANIFEST
NETWORK:
login.aspx

Aktualizacja pamięci podręcznej poprzez manifest


Kiedy przeglądarka po raz pierwszy widzi plik manifestu, pobiera pliki z listy i nie odświeża pamięci
podręcznej, dopóki tego nie wymusisz. Możesz to zrobić za pomocą API, co omówimy później, przez
wyczyszczenie pamięci podręcznej przeglądarki (Firefox ma możliwość usuwania manifestu niezależnie
dla każdej witryny — wybierz Firefox/Opcje/Zaawansowane) albo przez modyfikację pliku manifestu.
Aktualizacja pliku wymienionego w manifeście nie wymusi aktualizacji — należy zmienić sam manifest.
Zamiast dodawać albo usuwać zasoby, wystarczy dopisać bądź zmienić komentarz. Komentarz w pliku
można dopisać poprzez rozpoczęcie wiersza od znaku krzyżyka (#), co pokazano w poniższym przykładzie.
Komentarze muszą się znajdować w osobnych wierszach.
CACHE MANIFEST
# v1.0 - 06.28.2011

CACHE:
magic.js
style.css

Odświeżenie pamięci podręcznej może być wykonane po prostu przez zmianę komentarza.

PRZEPIS DLA POCZĄTKUJĄCYCH


Korzystanie ze stron internetowych offline
Po teoretycznych rozważaniach pora na przykład. W tej aplikacji sieciowej zaimplementujemy zwykłe
mnożenie dwóch podanych liczb i damy użytkownikowi możliwość użycia formularza kontaktowego, jeśli
Korzystanie ze stron internetowych offline 313

pracuje on online. Jeśli użytkownik pracuje offline, zostanie wyświetlona statyczna strona z informacjami
kontaktowymi. W tym przepisie wykorzystamy:
„ trzy strony HTML,
„ jeden plik javascriptowy,
„ dwa pliki CSS,
„ dwa obrazki.

Na pierwszej stronie HTML znajdują się dwa pola wejściowe (oczywiście z nowymi typami pól wejściowych
HTML5), a kliknięcie przycisku spowoduje pomnożenie obu liczb (w tym przykładzie nie ma walidacji).
Po kliknięciu przycisku Wyślij zostanie wyświetlony wynik oraz pojawi się możliwość zgłoszenia za pomocą
formularza kontaktowego obliczonego wyniku do fikcyjnej firmy. Formularz kontaktowy zostanie jednak
wyświetlony jedynie wówczas, gdy aktywne jest połączenie internetowe, w związku z czym zadeklarowana
zostanie rezerwowa możliwość (FALLBACK), którą jest strona zawierająca wyłącznie numer telefonu.
W tej aplikacji wykorzystujemy również plik CSS, który określa pewne style układu, oraz inny plik CSS,
który ładuje ładną czcionkę (mogłaby ona pochodzić na przykład z usługi oferującej czcionki internetowe).
Chcemy, aby ta ładna czcionka była wyświetlana tylko wówczas, gdy użytkownik pracuje online.
Ponadto strona będzie zawierać obrazek, który pokaże Ci, czy użytkownik jest online (znak wyboru),
czy offline (znak X).
Listing 13.3 pokazuje kod HTML dla tej aplikacji, a listing 13.4 prezentuje plik manifestu. Na rysunku
13.4 możesz zobaczyć aplikację w akcji.

Listing 13.3. Strona HTML wyświetlająca formularz z obliczeniami

<!DOCTYPE html>
<html manifest="listing.13.4.appcache">
<head>
<meta charset="utf-8">
<title>Offline</title>
<script src="13_3_calculation.js"></script>
<link rel="stylesheet" href="13_3_offline_style.css" />
<link rel="stylesheet" href="13_3_offline_style2.css" />
</head>
<body>
<h1>Oblicz</h1>
<label for="first">Pierwsza liczba</label>
<input required type="number" id="first" name="first" />
<label for="second">Druga liczba</label>
<input required type="number" id="second" name="second" />
<input type="submit" onclick="calculate()" value="Oblicz" />
<div id="resultHolder">
<h2>Otrzymany wynik to <span id="result"></span></h2>
<p>Znając już wynik, możesz <a href="13_3_offline_contact.html">się z nami skontaktować</a></p>
</div>
<img src="connection-tick.gif" width="50" height="50" alt="">
</body>
</html>
314 Rozdział 13. Zachowanie przeglądarek w HTML5

Listing 13.4. Manifest aplikacji obliczającej

CACHE MANIFEST
# Version 1.0

CACHE:
13_3_calculation.js
13_3_offline_style.css

NETWORK:
13_3_offline_style2.css

FALLBACK:
connection-tick.gif connection-cross.gif
13_3_offline_contact.html 13_3_offline_offline.html

Rysunek 13.4. Strona do wysyłania obliczeń podczas pracy online

Jeśli wypróbujesz ten przykład podczas pracy online, zobaczysz ładną czcionkę Comic Sans oraz graficzny
znak wyboru (patrz rysunek 13.4); jeśli jednak przejdziesz do pracy offline i odświeżysz stronę, aplikacja nie
będzie miała przypisanej czcionki i będzie pokazywać znak X (patrz rysunek 13.5). Jeśli zakończysz obliczenia
i spróbujesz uzyskać dostęp do formularza kontaktowego, zamiast niego zobaczysz stronę „Zadzwoń do nas”,
pokazaną na rysunku 13.6.

Rysunek 13.5. Strona do wysyłania obliczeń podczas pracy offline

Rysunek 13.6. Strona zastępcza dla formularza kontaktowego podczas pracy offline
API pamięci podręcznej aplikacji 315

W przypadku wykorzystywania pamięci podręcznej aplikacji dostępnych jest wiele możliwości. Twoja
aplikacja może się w całości znajdować na jednej stronie, ale może także być rozłożona na kilka stron, więc
musisz rozważyć, które pliki zapisać, a które wymagają dostępu do internetu.
Istnieją pewne różnice pomiędzy przeglądarkami. IE9 nie obsługuje w pełni aplikacji internetowych
offline. Choć wydaje się prawidłowo zapisywać początkową stronę, CSS oraz JavaScript w pamięci podręcznej
i działa offline, nie powinieneś polegać na poprawnym działaniu tej pamięci. Chrome, Firefox i Safari
działają zgodnie z oczekiwaniami i wyświetlają zastępczy obrazek oraz stronę HTML. Opera 11 wyświetla
zastępczy obrazek, ale przy próbie połączenia się z formularzem zamiast strony zastępczej wyświetla stronę
błędu połączenia.

API pamięci podręcznej aplikacji


Gdy przeglądarka ładuje ponownie stronę, do której przypisany jest manifest, najpierw sprawdza, czy pojawiły
się jakieś zmiany, a następnie, jeśli zmiany w pliku wystąpiły, wykonuje w tle aktualizację. Jeśli otworzysz
stronę z manifestem w przeglądarce Chrome, a następnie uruchomisz konsolę JavaScript i odświeżysz stronę,
zobaczysz na niej następujące informacje:
Document was loaded from Application Cache with manifest http://localhost/listing.13.4.appcache
Application Cache Checking event
Application Cache NoUpdate event

Jeśli zmienisz manifest i ponownie odświeżysz stronę, zobaczysz więcej komunikatów:


Document was loaded from Application Cache with manifest http://localhost/listing.13.4.appcache
Application Cache Checking event
Application Cache Downloading event
Application Cache Progress event (0 of 5) http://localhost/13_3_calculation.js
Application Cache Progress event (1 of 5) http://localhost/13_3_offline_style.css
Application Cache Progress event (2 of 5) http://localhost/listing.13.3.html
Application Cache Progress event (3 of 5) http://localhost/13_3_offline_offline.html
Application Cache Progress event (4 of 5) http://localhost/connection-cross.gif
Application Cache Progress event (5 of 5)
Application Cache UpdateReady event

W obiekcie ApplicationCache znajduje się kilka wymienionych poniżej zdarzeń. Domyślnie przeglądarka
udostępnia zdarzenie ładowania strony, ale API pamięci podręcznej aplikacji pozwala wykorzystać inne
zdarzenia:
„ checking — przeglądarka pobiera manifest po raz pierwszy albo sprawdza, czy plik był modyfikowany;
„ noupdate — manifest nie zmienił się;
„ downloading — jeśli przeglądarka pierwszy raz odwołuje się do manifestu, pobiera odpowiednie
zasoby; w przeciwnym przypadku pobiera aktualizację po jej wykryciu;
„ progress — przeglądarka pobiera wymieniony zasób;
„ cached — zasoby zostały pobrane i zapisane;
„ updateready — zasoby z manifestu zostały pobrane, a pamięć podręczna przeglądarki może
zostać uaktualniona; można następnie wywołać funkcję swapCache, która musi być wywołana,
aby przeglądarka używała najnowszego manifestu — inaczej przeglądarka będzie nadal korzystać
ze starej pamięci podręcznej;
316 Rozdział 13. Zachowanie przeglądarek w HTML5

„ obsolete — nie można odnaleźć pliku manifestu;


„ error — nie można znaleźć pliku manifestu, strona wymieniona w manifeście nie została prawidłowo
załadowana albo podczas sprawdzania manifestu wystąpił błąd krytyczny.

Powyższe zdarzenia dają możliwość poinformowania użytkownika, kiedy aktualizacja jest gotowa,
a następnie wywołania funkcji swapCache. Możesz tego dokonać, używając metody addEventListener:
applicationCache.addEventListener('updateready', function() {
//zrób coś
alert("Aktualizacja pamięci podręcznej")
applicationCache.swapCache();
})

Notification API
Notification API, znane również jako Web Notifications API, jest nowym API, które pozwala stronom
przeglądarki przekazywać użytkownikom informacje za pomocą powiadomień na pulpicie. API to jest
stosunkowo młode i aktualnie obsługiwane jedynie przez specyfikację WebKit i oparte o nią przeglądarki.
Wywołania za pomocą interfejsu powiadomień silnika WebKit są podobne do tych zdefiniowanych w roboczej
specyfikacji W3C, więc kiedy Web Notifications API zostanie już zaimplementowane we wszystkich
przeglądarkach, zmiany powinny być minimalne.
Podczas korzystania z innych aplikacji użytkownicy często umieszczają okno przeglądarki w tle. Kiedy
strona przeglądarki powiadamia użytkownika, że jego sesja niedługo utraci ważność, kurs akcji osiągnął
poziom, przy którym należy je sprzedać, czy też zaszło inne istotne dla użytkownika zdarzenie, powiadomienie
to może być przesłonięte innymi oknami. Za pomocą Notification API strona przeglądarki może teraz poprosić
pulpit użytkownika o wyświetlenie powiadomienia w jej imieniu. Powiadomienie zostanie wyświetlone
w sposób podobny do powiadomień z innych zainstalowanych aplikacji, zwykle w rogu ekranu.
Notification API silnika WebKit udostępnia dwa interfejsy: NotificationCenter i Notification. Interfejs
NotificationCenter daje możliwość poproszenia użytkownika o pozwolenie na wyświetlanie powiadomień,
odczytania statusu tego pozwolenia oraz tworzenia powiadomień. Interfejs ten obejmuje następujące metody:
„ Notification createNotification(in DOMString urlIkony, in DOMString tytuł, in DOMString
treść) — tworzy instancję powiadomienia na podstawie przekazanych w parametrach danych
(ikony, tytułu i treści powiadomienia).
„ Notification createHTMLNotification(in DOMString url) — tworzy powiadomienie w oparciu
o stronę HTML.
„ Int checkPermission() — pobiera poziom uprawnień dotyczący powiadomień dla bieżącej domeny.
„ void requestPermission([in Function wywołanieZwrotne]) — prosi użytkownika o pozwolenie
na wyświetlanie powiadomień.

Interfejs NotificationCenter jest udostępniany w przeglądarkach z silnikiem WebKit za pomocą obiektu


window poprzez odwołanie do jego atrybutu o nazwie webkitNotifications. Aby na przykład utworzyć
powiadomienie z JavaScriptu, można użyć następującego wiersza:
window.webkitNotifications.createNotification('ikona.png', 'Mój tytuł', 'Moje powiadomienie');
Notification API 317

Jak łatwo zauważyć, do tworzenia powiadomień służą dwie różne metody interfejsu: createNotification
i createHTMLNotification. Metoda createNotification pobiera parametry wykorzystywane do wyświetlenia
standardowego powiadomienia pulpitu: ikonę, tytuł i treść. Nie udostępnia ona innego sposobu dostosowania
powiadomienia do własnych potrzeb oprócz podania tych wartości. W przypadku createHTMLNotification
do metody jest przekazywany adres URL strony HTML. Strona znajdująca się pod tym adresem może posiadać
znaczniki formatujące i inne elementy realizujące dodatkowe funkcje, na przykład łącza internetowe. W obu
przypadkach zwracana jest instancja interfejsu Notification.
Po utworzeniu instancji do sterowania wyświetlaniem powiadomienia można wykorzystać metody
interfejsu Notification. W interfejsie Notification z silnika WebKit znajdują się dwie metody, show i cancel,
opisane poniżej:
„ show — dodaje powiadomienie do kolejki pulpitu w celu wyświetlenia, jeśli uprawnienia
na to pozwalają.
„ cancel — usuwa powiadomienie z pulpitu lub kolejki.

Interfejs Notification posiada również funkcje obsługi zdarzeń udostępniających szczegółowe informacje:
„ ondisplay — powiadomienie zostało wyświetlone na pulpicie.
„ onclose — powiadomienie zostało zamknięte — ręcznie lub za pomocą metody cancel().
„ onerror — wystąpił błąd dotyczący powiadomienia.

W ostatnim przepisie tego rozdziału zajmiemy się wykorzystaniem zdarzeń interfejsu Notification.

Uprawnienia dotyczące powiadomień


Jako że Notification API może w dowolnym momencie wyświetlać na ekranie użytkownika wyskakujące
okienka dowolnego rodzaju, naturalna wydaje się obawa dotycząca bezpieczeństwa i niechcianych treści
reklamowych. Notification API obejmuje jednak system uprawnień, zgodnie z którym użytkownik jest
najpierw proszony o zezwolenie na powiadomienia określonego pochodzenia. Kiedy wymagane jest zezwolenie
użytkownika, przeglądarka prosi o pozwolenie za pomocą wysuwającego się paska lub podobnego sposobu,
jak pokazano na rysunku 13.7.

Rysunek 13.7. Zapytanie o zezwolenie na pokazywanie powiadomień w przeglądarce Chrome

Weź pod uwagę, że prośba o udzielenie pozwolenia może zostać wyświetlona jedynie po celowym działaniu
użytkownika, takim jak kliknięcie przycisku albo łącza. Prośba o pozwolenie po załadowaniu strony nie da
żadnego efektu i nie zostanie wyświetlona użytkownikowi z powodów związanych z bezpieczeństwem. Ważne
jest także, aby uświadomić sobie, że pozwolenia na powiadomienia są ustawiane dla domen, a nie stron.
Jeśli więc użytkownik zablokuje powiadomienia dla jednej z Twoich stron, będą one blokowane dla całej
domeny. Jeśli użytkownik nie pozwolił na wyświetlanie powiadomień, a mimo to zostały one utworzone, próba
powiadomienia nie zakończy się niepowodzeniem, ale nie zostanie ono również wyświetlone. W ostatnim
przepisie tego rozdziału dowiesz się, jak sprawdzić aktualny „poziom uprawnień” wybrany przez użytkownika
dla domeny.
318 Rozdział 13. Zachowanie przeglądarek w HTML5

Kompatybilność przeglądarek
Tabela 13.3 przedstawia wersję każdej z przeglądarek obsługujących Notification API. Zauważ jednak,
że Notification API jest zaimplementowany w sposób specyficzny dla silnika WebKit, ponieważ ogólna
specyfikacja ma status wersji roboczej. Oczekuje się, że ogłoszona ostatecznie specyfikacja będzie opisywała
sposób pokazywania powiadomień bez wykorzystywania silnika WebKit.

Tabela 13.3. Dostępność Notification API w najpopularniejszych przeglądarkach

Android -
Chrome 10.0+
Firefox -
Internet Explorer -
iOS Safari -
Opera -
Safari -

PRZEPIS DLA POCZĄTKUJĄCYCH


Wyświetlanie prostego powiadomienia
W pierwszym przepisie użyjemy powiadomień silnika WebKit w celu poproszenia użytkownika o pozwolenie
na ich pokazywanie, a następnie do utworzenia i wyświetlenia powiadomienia, kiedy użytkownik kliknie
przycisk Utwórz powiadomienie. Użytkownik może uruchomić tyle powiadomień, ile chce, a każde wyświetlone
powiadomienie będzie miało unikalną, powiększającą się z każdym nowym powiadomieniem liczbę. Jeśli pulpit
osiągnie limit dotyczący liczby jednocześnie wyświetlanych powiadomień, będą one oczekiwały w kolejce
na wyświetlenie.
To prosty przepis, ale dzięki niemu poznasz podstawy wyświetlania powiadomień na pulpicie. Utwórzmy
pierwsze powiadomienie przy użyciu listingu 13.5:
1. Utwórz pustą stronę HTML, zawierającą znaczniki pokazane na listingu 13.5 oraz przyciski
Ustaw uprawnienia i Utwórz powiadomienie.
2. Dodaj wiersz zawierający window.addEventListener dla zdarzenia load dotyczącego strony
oraz funkcję init, która wiąże procedury obsługi zdarzeń kliknięcia z przyciskami.
3. Dodaj zmienną globalną notificationCount, aby każde powiadomienie miało unikalny
identyfikator.
4. Dodaj funkcję setPermission, aby prosić o uprawnienia, jeśli interfejs webkitNotifications
jest obsługiwany.
5. Dodaj funkcję fireNotification, która tworzy, a następnie wyświetla powiadomienie.

Listing 13.5. Użycie createNotification do wyświetlenia powiadomienia

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
Wyświetlanie prostego powiadomienia 319

<title>13.5. Proste powiadomienia</title>


<script>
// licznik powiadomień
var notificationCount = 0;

// inicjalizuj stronę
function init() {

// odwołania do przycisków
var btnSetPermission = document.getElementById('setPermission');
var btnFireNotification = document.getElementById('fireNotification');

// ustaw procedury obsługi zdarzeń kliknięcia przycisków


btnSetPermission.addEventListener('click',setPermission,false);
btnFireNotification.addEventListener('click',fireNotification,false);
}

// poproś użytkownika o zezwolenie na pokazywanie powiadomień


function setPermission() {
// sprawdź, czy webkitNotifications jest obsługiwany
if (webkitNotifications) {
// poproś użytkownika o pozwolenie
window.webkitNotifications.requestPermission();
} else {
// poinformuj użytkownika, że powiadomienia nie są obsługiwane
alert("Powiadomienia nie są obsługiwane w tej przeglądarce.");
}
}

// funkcja tworząca i pokazująca powiadomienie


function fireNotification() {
// powiększ o 1 licznik powiadomień
notificationCount++;

// utwórz powiadomienie, podając ikonę, tytuł i ciało


var notification = webkitNotifications.createNotification(
'icon_notification.png',
'Numer '+notificationCount,
'To jest powiadomienie numer '+notificationCount);

// teraz pokaż samo powiadomienie


notification.show();
}

// dodaj procedurę nasłuchu zdarzenia load w celu zainicjalizowania strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Przepis na proste powiadomienia</h1>
<p>Kliknij „Ustaw uprawnienia”, by zezwolić na pojawianie się powiadomień, a potem utwórz powiadomienie.</p>
<section>
<button id="setPermission">Ustaw uprawnienia</button>
<button id="fireNotification">Utwórz powiadomienie</button>
</section>
</body>
</html>
320 Rozdział 13. Zachowanie przeglądarek w HTML5

Po załadowaniu kodu z listingu 13.5 do przeglądarki zostanie uruchomiona funkcja init, która doda
procedury obsługi zdarzeń kliknięcia do obu przycisków: Ustaw uprawnienia i Utwórz powiadomienie.
Najpierw kliknij przycisk Ustaw uprawnienia, aby przeglądarka wyświetliła pytanie o pozwolenie na
wyświetlanie powiadomień. (Pamiętaj, aby uruchomić przepis w Chrome albo innej przeglądarce bazującej
na silniku WebKit). Po wyświetleniu zapytania o uprawnienia wybierz opcję zezwolenia na wyświetlanie
powiadomień, co poinformuje przeglądarkę, że każda ze stron domeny może żądać wyświetlenia
powiadomienia na pulpicie. W ramach funkcji setPermission sprawdzamy najpierw, czy webkitNotifications
jest dostępny. Jeśli tak, skrypt wykonuje metodę requestPermission tego interfejsu. Spowoduje to wyświetlenie
użytkownikowi pytania dotyczącego uprawnień. Jeśli wcześniej zezwoliłeś na wyświetlanie powiadomień,
ponowne wywołanie requestPermission nie będzie miało żadnych skutków. W celach testowych można
usunąć uprawnienie dotyczące danej domeny, co jest opisane w poniższej wskazówce.

Wskazówka
Podczas pracy z powiadomieniami zauważysz, że po zablokowaniu lub zezwoleniu na wyświetlanie
powiadomień nie można już użyć metody requestPermission do ponownego zapytania użytkownika
o zgodę lub jej brak. Aby wyczyścić preferencje dotyczące powiadomień, należy przejść do ustawień
przeglądarki i ręcznie usunąć pozwolenie. W przeglądarce Chrome opcję tę można znaleźć poprzez
Ustawienia/Pokaż ustawienia zaawansowane…/Prywatność — Ustawienia treści…/Powiadomienia/
Zarządzaj wyjątkami…, jak pokazano na rysunku 13.8.

Rysunek 13.8. Ustawienia dotyczące zarządzania powiadomieniami w Chrome

Aby wyczyścić informację o uprawnieniach domeny dotyczących powiadomień, przejdź do odpowiedniego


wpisu i kliknij X, aby usunąć go z listy Wzorzec nazwy hosta, jak pokazano na rysunku 13.9. Spowoduje
to usunięcie uprawnienia i pozwoli JavaScriptowi zadać zapytanie za pomocą metody requestPermission.
Po zezwoleniu na wyświetlanie powiadomień możesz kliknąć przycisk Utwórz powiadomienie. Uruchomi
to metodę fireNotification, która zwiększy wartość licznika powiadomień o 1, a następnie za pomocą metody
createNotification utworzy powiadomienie. Metodzie createNotification przekazujemy utworzony przez
nas adres URL ikony, tytuł zawierający wartość licznika oraz tekst powiadomienia, który również zawiera tę
unikalną wartość. Po utworzeniu powiadomienia należy jeszcze wywołać metodę show, aby przeglądarka
poprosiła pulpit o wyświetlenie powiadomienia. Jeśli zgoda nie została udzielona, powiadomienie nie zostanie
wyświetlone.
Po każdorazowym kliknięciu przycisku Utwórz powiadomienie zostanie utworzone i wyświetlone nowe
powiadomienie. Na rysunku 13.10 widoczny jest przykład kilkakrotnego kliknięcia tego przycisku.
Wyświetlanie prostego powiadomienia 321

Rysunek 13.9. Lista Wzorzec nazwy hosta w przeglądarce Chrome, pozwalająca usunąć
informację o uprawnieniach dotyczących powiadomień

Rysunek 13.10. Wyświetlanie powiadomień na pulpicie

Uwaga
Każdy system ma ograniczenie co do liczby jednocześnie wyświetlanych powiadomień. Jeśli utworzysz
powiadomienie, a pulpit wyświetla aktualnie ich maksymalną liczbę, to nowe powiadomienie zostanie
umieszczone w kolejce i będzie tam, dopóki użytkownik nie usunie z pulpitu któregoś z wyświetlanych
powiadomień albo nie zostanie ono usunięte automatycznie.

W tym przepisie poznaliśmy prosty przykład tworzenia i wyświetlania powiadomień na pulpicie za


pomocą powiadomień silnika WebKit. W kolejnym przepisie wykorzystamy te i inne sposoby oraz zdarzenia
do utworzenia powiadomień o nowych tweetach na Twitterze.
322 Rozdział 13. Zachowanie przeglądarek w HTML5

PRZEPIS DLA ZAAWANSOWANYCH


Tworzenie strony powiadomień o tweetach
Po przeczytaniu poprzedniego przepisu, w którym tworzyliśmy powiadomienia na pulpicie, masz
prawdopodobnie pewne pomysły dotyczące sposobów użycia Notification API. Możliwość powiadamiania
użytkownika o jakimś zdarzeniu, podczas gdy nie używa on okna przeglądarki, wydaje się całkiem użyteczna.
W tym przepisie wykorzystamy Notification API do wyświetlania na pulpicie powiadomień o nowych
tweetach konkretnego użytkownika witryny Twitter.com. Przepis wykorzysta metodę statuses/user_timeline
API Twittera do pobrania przez użytkownika nowych tweetów, a następnie wyświetlenia ich na pulpicie jako
powiadomień. Strona będzie okresowo sprawdzać, czy pojawiły się nowe tweety użytkownika, wysyłając
co 60 sekund zapytania stronie Twitter.com. Przy pierwszym zapytaniu strona pobierze domyślnie trzy
ostatnie tweety. Aby uzyskać więcej informacji na temat API Twittera, sprawdź dokumentację dla REST
API, pod adresem http://dev.twitter.com/doc, oraz dokumentację wykorzystanej w tym przepisie metody
API, pod adresem https://dev.twitter.com/docs/api/1/get/statuses/user_timeline.
Rysunek 13.11 przedstawia wynik działania kodu w postaci kilku ostatnich tweetów wyświetlonych
jako powiadomienia na pulpicie.

Rysunek 13.11. Wyświetlanie tweetów jako powiadomień na pulpicie

Uwaga
Twitter API ma możliwość wykonywania za pośrednictwem Twitter REST API funkcji zwrotnych. W tym
przepisie wykorzystamy tę technikę do wykonywania bezpośrednich wywołań javascriptowych do API
Twittera. Z tego sposobu oraz formatu JSONP musimy skorzystać w celu obejścia kwestii bezpieczeństwa
żądań pomiędzy różnymi domenami, które występują zwykle przy podobnych żądaniach przesyłanych
przez klienta. Do wywołania metody można wykorzystać zewnętrzną bibliotekę, taką jak jQuery, lub
nawet zbudować w domenie własny serwer pośredniczący. Zauważ, że w celu ochrony przed nadużyciami
API Twittera ma, tak jak większość internetowych API, ograniczenia częstości wywołań, więc jeśli
będziesz go używał zbyt często, możesz otrzymać błąd „over rate limit” („przekroczone ograniczenie
częstości wywołań”).

Oprócz metod createNotification i show, które poznałeś w poprzednim przepisie, użyjemy również
metody cancel, dzięki której po pewnym czasie automatycznie usuniemy powiadomienia. W celu
Tworzenie strony powiadomień o tweetach 323

automatycznego usunięcia wyświetlonego powiadomienia po 15 sekundach skorzystamy z procedury


obsługi zdarzenia ondisplay. Następnie skrypt odczeka minutę i zapyta Twittera o nowe tweety.
Być może zauważyłeś, że w poprzednim przepisie nie było jasne, czy zezwolenie zostało udzielone
ani jaki jest aktualny poziom uprawnień dotyczący powiadomień. W tym przepisie użyjemy metody
checkPermission interfejsu webkitNotifications do umożliwienia użytkownikowi sprawdzenia uprawnień
w dowolnym momencie. Nie jest to coś, co będziesz zwykle udostępniał na swoich stronach użytkownikowi,
ale może być przydatne. Metoda checkPermission zwraca wartość liczbową opisującą aktualny stan uprawnień
dotyczących powiadomień pulpitu, jak pokazano w tabeli 13.4.

Tabela 13.4. Poziomy uprawnień dla powiadomień pulpitu

Uprawnienie Wartość Opis


PERMISSION_ALLOWED 0 Użytkownik udzielił zezwolenia na powiadomienia pulpitu.
PERMISSION_NOT_ALLOWED 1 Zwykle oznacza, że użytkownik nie został zapytany lub nie udzielił
odpowiedzi na pytanie o uprawnienia.
PERMISSION_DENIED 2 Użytkownik zablokował powiadomienia pulpitu z tej domeny.

Zwykle, aby wywołać zewnętrzną usługę sieciową z użyciem funkcji zwrotnej, zapisujemy adres URL
wywołania REST w atrybucie src znacznika script. W tym przepisie przedstawimy interesujący alternatywny
sposób z wykorzystaniem wątku roboczego (patrz rozdział 12.), który za pomocą funkcji importScripts
otrzymuje wyniki, parsuje je, a następnie przesyła z powrotem do głównego wątku. Dzięki temu główny
wątek nie jest blokowany i może podczas odwoływania się do Twittera kontynuować pracę, a wyniki zostają
przetworzone — po prostu za każdym razem, kiedy chcesz pobrać wyniki, tworzony jest nowy wątek roboczy,
który po przetworzeniu wyników kończy działanie. Wątek roboczy nie może jednak utworzyć powiadomienia,
gdyż potrzebowałby w tym celu obiektu window, który nie jest dostępny w wątku roboczym. Odpowiednia
informacja jest wobec tego wysyłana do głównego wątku, a ten pokazuje powiadomienie. W związku
z przyjęciem takiego modelu działania kod przepisu został podzielony na dwa pliki. Listing 13.6 przedstawia
główną stronę, a listing 13.7 kod wątku roboczego. Zacznijmy od strony głównej przedstawionej na listingu 13.6:
1. Utwórz pustą stronę z odpowiednimi znacznikami i przyciskami Ustaw uprawnienia, Sprawdź
poziom uprawnień oraz Pobierz tweety.
2. Dodaj znaczniki script oraz zmienne globalne tworker i lastTweetId.
3. Dodaj funkcję setPermission, służącą do poproszenia o zezwolenie na wyświetlanie powiadomień
na pulpicie.
4. Dodaj funkcję checkPermissionLevel, służącą do pobierania i wyświetlania bieżącego poziomu
uprawnień.
5. Dodaj funkcję grabTweets, która uruchamia wątek roboczy, przypisuje procedurę nasłuchu
dotyczącą jego komunikatów oraz obsługuje tworzenie powiadomień.

Listing 13.6. Tworzenie powiadomień w głównym wątku

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>13.6. Powiadomienia z Twittera</title>
324 Rozdział 13. Zachowanie przeglądarek w HTML5

<script>

// ustaw odwołanie do wątku roboczego


var tworker = null;
// ustaw identyfikator ostatniego przeczytanego tweeta
var lastTweetId = 0;

// funkcja inicjalizująca procedury obsługi


function init() {
// ustaw procedury obsługi
var btnSetPermission = document.getElementById('btnSetPermission');
var btnCheckPermissionLevel = document.getElementById('btnCheckPermissionLevel');
var btnGrabTweets = document.getElementById('btnGrabTweets');

btnSetPermission.addEventListener('click',setPermission,false);
btnCheckPermissionLevel.addEventListener('click',checkPermissionLevel,false);
btnGrabTweets.addEventListener('click',grabTweets,false);
}

// funkcja prosząca użytkownika o zezwolenie na wyświetlanie powiadomień


function setPermission() {

// sprawdź, czy przeglądarka obsługuje powiadomienia


if (webkitNotifications) {

// sprawdzanie powiadomień jeszcze nie jest dozwolone


if (webkitNotifications.checkPermission() == 1) {

// poproś o zezwolenie i wywołaj zwrotnie tę funkcję


webkitNotifications.requestPermission(setPermission);
} else if (webkitNotifications.checkPermission() == 2) {

// poinformuj, że użytkownik odmówił udzielenia pozwolenia


alert('Użytkownik odmówił udzielenia pozwolenia.');
} else {
// potwierdź, że pozwolenie zostało udzielone
alert('Pozwolenie zostało udzielone.');
}
} else {
// powiadomienia nie są obsługiwane
alert('Powiadomienia nie są obsługiwane w tej przeglądarce. ');
}
}

// sprawdź powiadomienia
function checkPermissionLevel() {

// sprawdź, czy powiadomienia są obsługiwane


if (webkitNotifications) {

// pobierz aktualny poziom uprawnień


var permissionLevel = webkitNotifications.checkPermission();

// mapuj poziom uprawnień na jego liczbowy odpowiednik


switch (permissionLevel) {
case 0:
// PERMISSION_ALLOWED
alert('Obecny poziom: PERMISSION_ALLOWED (0)');
break;
case 1:
// PERMISSION_NOT_ALLOWED
Tworzenie strony powiadomień o tweetach 325

alert('Obecny poziom: PERMISSION_NOT_ALLOWED (1)');


break;
case 2:
// PERMISSION_DENIED
alert('Obecny poziom: PERMISSION_DENIED (2)');
break;
}
}
}

// funkcja pobierająca tweety i tworząca powiadomienia


function grabTweets() {

// zapisz w dzienniku, że tworzymy wątek roboczy


console.log('tworzenie wątku roboczego');

// utwórz instancję wątku roboczego do pobierania tweetów użytkownika


tworker = new Worker('listing.13.7.js');

// procedura obsługi zdarzeń message dla komunikatów z wątku roboczego


tworker.addEventListener('message', function(msg) {

// ustal typ komunikatu z wątku roboczego


switch (msg.data.msgType) {

// otrzymany komunikat tweeta do wyświetlenia jako powiadomienie


case 'TWEET':

console.log('tworzenie powiadomienia');

// pobierz ikonę, tytuł i treść z komunikatu wątku roboczego


nIcon = msg.data.icon;
nTitle = msg.data.title;
nBody = msg.data.body;

// utwórz powiadomienie
var notification = webkitNotifications.createNotification(nIcon, nTitle, nBody);

// ustaw procedurę obsługi zdarzenia ondisplay


notification.ondisplay = function(event) {

// odtwórz dźwięk powiadomienia


var audio = new Audio('13_6_notify.mp3');
audio.play();

// ustaw ograniczenie czasowe, aby usunąć powiadomienie po 15 sekundach


setTimeout(function() {event.currentTarget.cancel()}, 15000);
}

// ustaw procedurę obsługi zdarzenia onclose


notification.onclose = function() {
// zapisz w dzienniku, że powiadomienie zostało zamknięte
console.log('Powiadomienie zamknięte.');
}

// ustaw procedurę obsługi zdarzenia onerror


notification.onerror = function() {
// zapisz w dzienniku, że wystąpił błąd związany z powiadomieniem
console.log('Błąd powiadomienia.');
}
326 Rozdział 13. Zachowanie przeglądarek w HTML5

// wyświetl powiadomienie
notification.show();

break;

// wątek roboczy zakończył działanie


case 'END':

// jeśli mamy ostatni tweet, zachowaj jego identyfikator


if (msg.data.lastTweetId>0) {
lastTweetId = msg.data.lastTweetId;
}

// ustaw limit czasu, aby wyszukiwać ponownie za 1 minutę


setTimeout('grabTweets()',60000);

break;
}
}, false);

// uruchom wyszukiwanie przez wątek roboczy z identyfikatorem ostatniego tweeta


tworker.postMessage(lastTweetId);
}

// procedura obsługi zdarzenia load okna


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Tworzenie powiadomień z nowych tweetów</h1>
Najpierw kliknij „Ustaw uprawnienia”, aby zezwolić na pokazywanie powiadomień.<br>
Następnie kliknij „Pobierz tweety”, aby rozpocząć pobieranie nowych tweetów.
<br><br>
<button id="btnSetPermission">Ustaw uprawnienia</button>
<button id="btnCheckPermissionLevel">Sprawdź poziom uprawnień</button>
<button id="btnGrabTweets">Pobierz tweety</button>
</body>
</html>

Po ukończeniu strony głównej należy utworzyć plik javascriptowy wątku roboczego służącego
do pobierania i parsowania tweetów — patrz listing 13.7:
1. Utwórz plik .js wątku roboczego z procedurą obsługi zdarzeń onmessage, dotyczących otrzymywania
komunikatów z głównej strony. Obsługa zdarzenia onmessage uwzględnia również import skryptu
API REST ze strony Twitter.com.
2. Dodaj funkcję getResult, która jest funkcją wywołania zwrotnego z wywołania API REST z funkcji
importScripts. Funkcja getResult będzie obsługiwać parsowanie wszystkich pobranych tweetów
i wysyłanie ich do głównego wątku w celu wyświetlenia. I to tyle, jeśli chodzi o wątek roboczy.

Listing 13.7. Wykorzystanie wątku roboczego do śledzenia tweetów

// listing.13.7.js
// Wątek roboczy pobierający tweety

// funkcja zwrotna do parsowania pobranych tweetów


function getResult(data) {
Tworzenie strony powiadomień o tweetach 327

// zmienna przechowująca największy identyfikator z listy tweetów


var maxId = 0;

// sprawdź, czy są tweety, które należy parsować


if (data.length>0) {

// zmienne dotyczące powiadomień


var tIcon = '';
var tTitle = '';
var tBody = '';

// przejdź w pętli przez zbiór wyników w odwrotnej kolejności


for (var i=(data.length-1); i>=0; i--) {

// pobierz obrazek profilu, nazwę do wyświetlenia i tweet


tIcon = data[i].user.profile_image_url;
tTitle = data[i].user.screen_name;
tBody = data[i].text;

// zwróć informację o tweecie z powrotem do głównego wątku w celu wyświetlenia tweeta


postMessage({msgType:'TWEET',icon:tIcon,title:tTitle,body:tBody});

// zapisz id_str jako największy identyfikator, aby nie pokazywać tweetów ponownie
maxId = data[i].id_str;
}
}

// poinformuj główny wątek, że w tej pętli to wszystko, co było do zrobienia


postMessage({msgType:'END',lastTweetId:maxId});
}

// przechwyć komunikat z głównego wątku


onmessage = function(event) {

// pobierz identyfikator ostatniego tweeta


var lastId = event.data;
var qString = '';

// sprawdź, czy identyfikator ostatniego tweeta to identyfikator istniejącego tweeta


if (lastId != 0) {
// jeśli to identyfikator istniejącego tweeta, użyj since_id
qString = '&since_id='+lastId;
} else {
// brak wcześniej przeczytanych tweetów, więc pobierz maksymalnie trzy
qString = '&count=3';
}

// użyj importScripts jako metody JSONP zamiast znacznika script


// obecnie wyświetlane są tweety dla screen name mającego wartość HelionPL, ale możesz to zmienić
// na jakiegokolwiek użytkownika Twittera, którego tweety chciałbyś wyświetlać
importScripts('http://api.twitter.com/1/statuses/user_timeline.json?
´screen_name=HelionPL'+qString+'&callback=getResult');

Po załadowaniu w przeglądarce głównej strony, ale przed kliknięciem przycisku Ustaw uprawnienia,
kliknij przycisk Sprawdź poziom uprawnień. Spowoduje to wywołanie funkcji checkPermissionLevel, która
sprawdzi, czy webkitNotifications jest dostępny, a następnie wykona metodę checkPermission, pobierając
328 Rozdział 13. Zachowanie przeglądarek w HTML5

aktualny poziom uprawnień w postaci wartości liczbowej, oraz wyświetli odpowiadające tej wartości
wyjaśnienie w postaci okna dialogowego. Dzięki temu szybko stwierdzisz, czy uprawnienia do wyświetlania
powiadomień na pulpicie są dla domeny poprawnie nadane. Możesz poćwiczyć, usuwając uprawnienia
w ustawieniach przeglądarki, blokując powiadomienia, zezwalając na nie oraz oglądając zmiany poziomu
uprawnień. Jeśli nie nadałeś uprawnień do wyświetlania powiadomień, zrób to przed kontynuowaniem
pracy z przepisem.
Po wczytaniu kodu skrypt ustawia globalne odwołanie do wątku roboczego o nazwie tworker oraz
zmienną globalną o nazwie lastTweetId, która będzie przechowywać „zakładkę” dotyczącą ostatniego
tweeta odczytanego dla użytkownika. Podczas ładowania strony skrypt ustawia wartość tej zmiennej na zero,
co oznacza, że strona została właśnie załadowana i żadne tweety nie zostały odczytane. Po uruchomieniu
wątku roboczego w celu pobrania listy tweetów zostanie mu przekazana wartość zmiennej lastTweetId
służąca do określenia, które tweety należy wyświetlić. Wartość tej zmiennej należy przechowywać na głównej
stronie, gdyż po zakończeniu pobierania i parsowania tweetów wątek roboczy zniknie. Przy każdym
odpytywaniu o tweety tworzony jest nowy wątek roboczy.
Aby rozpocząć proces pobierania tweetów dla użytkownika i wyświetlania powiadomień, kliknij przycisk
Pobierz tweety. Spowoduje to uruchomienie funkcji grabTweets, która najpierw doda do dziennika konsoli
komunikat potwierdzający rozpoczęcie procesu pobierania tweetów, a następnie utworzy nowy wątek
roboczy z pliku listing.13.7.js. Wątek roboczy jest tak zaprojektowany, że nie podejmie żadnych działań,
dopóki za pomocą komunikatu nie zostanie poinstruowany, aby rozpocząć przetwarzanie, więc w tym
miejscu przyjrzyjmy się pozostałej części funkcji grabTweets. W kolejnym kroku funkcja ustawia procedurę
obsługi zdarzenia message wątku roboczego, tak aby po wysłaniu przez wątek roboczy komunikatu można
go było obsłużyć. Wykorzystany będzie tu podobny sposób przekazywania wiadomości jak w przypadku
wątków roboczych z przepisów z rozdziału 12. — wątek może wysyłać różne rodzaje komunikatów o treści
w formacie JSON, zawierającej typ komunikatu i związane z tym typem parametry.
Z wątku roboczego do głównej strony są przesyłane dwa różne rodzaje komunikatów: TWEET oraz END.
Jeśli otrzymasz msgType o wartości TWEET, oznacza to, że wątek roboczy znalazł tweet do wyświetlenia. W takim
przypadku pozostała część komunikatu posłuży do wyświetlenia powiadomienia zawierającego ikonę
z awatarem użytkownika, nazwę użytkownika jako tytuł oraz tweet jako treść powiadomienia. W tym
przepisie kod tworzy powiadomienie, dodaje procedury obsługi zdarzeń ondisplay, onclose oraz onerror
i w końcu wyświetla powiadomienie — procedury obsługi zdarzeń onclose i onerror po prostu zapisują
w dzienniku konsoli komunikat informujący o przechwyceniu zdarzenia; procedura obsługi zdarzenia
ondisplay służy do odtworzenia dźwięku przy wyświetleniu powiadomienia. Następnie zostaje ustawiony
limit czasu, aby automatycznie usunąć z pulpitu powiadomienie, jeśli użytkownik go jeszcze nie zamknął.
Wreszcie, metoda show() wyświetla powiadomienie.
Komunikat END będzie oznaczać, że wątek roboczy zakończył pobieranie i przetwarzanie tweetów.
Dzięki temu, że wątek roboczy informuje o zakończeniu pracy, możesz się upewnić, iż w tym samym czasie
nie działa wiele wątków, oraz odczekać pewien czas pomiędzy zakończeniem pracy przez jeden z wątków
a jej rozpoczęciem przez następny. Komunikat END może zawierać identyfikator ostatniego tweeta albo,
jeśli nie pobrano żadnych tweetów, wartość zero. W razie potrzeby skrypt aktualizuje zakładkę wartością
identyfikatora ostatniego tweeta, aby zaznaczyć, która wiadomość została pobrana jako ostatnia. Po ustawieniu
identyfikatora ostatniego tweeta skrypt — w celu ponownego uruchomienia funkcji grabTweets — ustawia
odmierzanie czasu. W tym przepisie nie zawracaliśmy sobie głowy dodawaniem funkcji zatrzymywania
działania, ale można to łatwo zrealizować — czy to przy użyciu flagi zapobiegającej przyszłemu pobieraniu
danych, czy też, w razie potrzeby, poprzez anulowanie działania wątku roboczego. Na końcu funkcji
Podsumowanie 329

grabTweets kod nakazuje wątkowi roboczemu rozpoczęcie pracy, przesyłając do niego komunikat zawierający
zakładkę lastTweetId.
Przejdźmy teraz do wątku roboczego i omówmy pokrótce proces pobierania tweetów użytkownika.
Wątek roboczy obejmuje zasadniczo dwie sekcje: żądanie podania informacji za pomocą procedury obsługi
zdarzenia onmessage oraz parsowanie zbioru wyników w funkcji getResult. Działanie wątku roboczego jest
uruchamiane za pomocą komunikatu zawierającego identyfikator ostatniego tweeta — procedura obsługi
onmessage wątku pobiera ten identyfikator i tworzy adres URL żądania REST, wykorzystując metodę
importScripts wątków. Podobnie jak przy wykorzystaniu znaczników script, importScripts po prostu
ładuje do wątku roboczego zewnętrzny skrypt albo zbiór skryptów. Pozwala to obejść kwestie dotyczące
bezpieczeństwa związane z pracą z różnymi domenami. W żądaniu URL używamy parametru callback
łańcucha zapytania, aby przekazać nazwę funkcji getResult, która ma być uruchomiona wraz z wynikami
żądania. Ponadto pewne parametry ustawiamy dynamicznie, zależnie od tego, czy chodzi o pierwsze, czy
kolejne żądanie. Dalszych wyjaśnień dotyczących opcji wyszukiwania szukaj w dokumentacji API Twittera.
Po wywołaniu funkcji zwrotnej getResult z wynikami JSON wątek roboczy sprawdza, czy są jakieś tweety,
badając, czy długość danych jest większa od zera. Jeśli tak jest, co oznacza obecność tweetów do sparsowania,
skrypt przechodzi w pętli przez każdy element w wynikach i wydobywa z niego adres URL obrazka, nazwę
użytkownika oraz sam tweet. Kiedy skrypt wydobędzie już wartości tych pól, wątek roboczy wysyła do
głównego wątku komunikat z informacjami dotyczącymi powiadomienia. Po wysłaniu komunikatu wątek
roboczy zapisuje identyfikator tweeta w zmiennej, aby w końcowej fazie przetwarzania wysłać ją do strony
głównej jako następną zakładkę, informującą, od którego tweeta należy zacząć. Można zauważyć, że skrypt
parsuje zbiór wyników z tweetami w odwrotnej kolejności. Wynika to z faktu, że pierwszy rekord reprezentuje
najnowszy tweet, a ostatni najstarszy — a skoro chcemy wyświetlać powiadomienia w porządku, w jakim
zostały napisane, muszą one być wyświetlane w odwrotnej kolejności.
W tym przepisie pokazaliśmy, w jaki sposób wyświetlać i automatycznie usuwać powiadomienia.
Zademonstrowaliśmy także, w jaki sposób łączyć je z innymi API, tak aby powiadamiać użytkowników
o zdarzeniach na stronie, nawet jeśli nie oglądają oni okna przeglądarki.

Podsumowanie
W tym rozdziale dowiedziałeś się o pewnych pomniejszych rozszerzeniach API. Przeciąganie i upuszczanie,
pamięć offline oraz powiadomienia pozwalają znacznie poprawić działanie witryn i aplikacji. Zapewniają
również wyższy poziom integracji, szybko niwelujący różnice między aplikacjami działającymi w przeglądarce
a aplikacjami natywnymi. Będziemy się z zainteresowaniem przyglądać, w jaki sposób rozszerzenia te będą
wykorzystywane w przyszłości.
330 Rozdział 13. Zachowanie przeglądarek w HTML5
14
Praca z plikami lokalnymi

P rzed pojawieniem się HTML5 działania na plikach przeprowadzane w przeglądarce były słabo obsługiwane
i nie wzbudzały dużego zainteresowania. Zakres funkcjonalności obejmował zwykle tylko prosty wybór
pliku, który miał być wysłany do serwera. Teraz, dzięki HTML5 i uzupełniającemu go File API, mamy do
dyspozycji powiększony zestaw funkcjonalności oraz, co ważniejsze, ustandaryzowany zestaw funkcji, który
pozwala odczytać różne rodzaje plików oraz ich podstawowe atrybuty w przeglądarce za pomocą JavaScriptu.
W tym rozdziale poznasz nowy zbiór funkcji dotyczących File API i wykorzystasz przepisy, w których omówimy
podstawy oraz pewne nowe specyfikacje File API pojawiające się na horyzoncie.

Przegląd File API


File API zapewnia ustandaryzowany sposób interakcji z plikami wybranymi przez użytkownika w przeglądarce.
Interfejsy File API można podzielić na trzy obszary funkcjonalne:
„ wybór pliku lub plików za pomocą interfejsu FileList,
„ pobieranie atrybutów plików poprzez interfejs File,
„ odczyt pliku poprzez interfejs FileReader.

Interfejs FileList jest listą plików wybranych przez użytkownika, po której elementach można po kolei
iterować, tak jak w przypadku tablic. Interfejs File udostępnia właściwości wybranego pliku. Za pomocą tych
dwóch interfejsów skrypty mogą przejść w pętli przez wybrane pliki i odczytać następujące najważniejsze
właściwości tych plików:
„ name — nazwa pliku,
„ type — typ pliku w formacie MIME i kodowaniu ASCII,
„ size — rozmiar pliku w bajtach,
„ lastModifiedDate — data i czas ostatniej modyfikacji pliku.

Bezpieczeństwo File API


File API przetwarza informacje i dane spoza przeglądarki w urządzeniu klienta. Ta interakcja z lokalnym
systemem plików w naturalny sposób budzi obawy związane z bezpieczeństwem. Jednak specyfikacja HTML5
File API pozwala JavaScriptowi na interakcję jedynie z plikami, które użytkownik udostępnił. File API nie
332 Rozdział 14. Praca z plikami lokalnymi

może się poruszać po strukturze katalogów albo wybierać plików samodzielnie, bez interakcji użytkownika
wybierającego plik. Tabela 14.1 pokazuje, które wersje każdej przeglądarki obsługują File API. Zakres obsługi
w poszczególnych przeglądarkach jest różny, o czym będziemy wspominać w odpowiednich miejscach
w przepisach tego rozdziału.

Tabela 14.1. Dostępność File API w przeglądarkach

Android 3.0+
Chrome 9.0+
Firefox 3.6+
Internet Explorer 10.0+
iOS Safari -
Opera 11.1+
Safari -

PRZEPIS DLA POCZĄTKUJĄCYCH


Odczyt atrybutów pliku
W tym przepisie wykorzystamy interfejs File do odczytania nazwy, rodzaju, rozmiaru i daty ostatniej
modyfikacji pliku, który został wybrany przez odwiedzającego stronę za pomocą standardowego elementu
input służącego do wyboru pliku. Zanim zademonstrujemy, jak uzyskać te informacje, pokażemy, jak
stwierdzić, czy interfejs FileList jest dostępny w przeglądarce. Aby wyświetlić atrybuty pliku, tak jak to
pokazano na listingu 14.1, wykonaj następujące kroki:
1. Utwórz pustą stronę HTML z elementem input służącym do wyboru pliku i dodaj procedurę
obsługi zdarzenia onchange, wywoływaną po wyborze pliku.
2. Dodaj do elementu body element section o identyfikatorze fileAttributes, w którym przepis
będzie wyświetlać atrybuty plików.
3. Dodaj funkcję handleFile przyjmującą parametr fileInput.

Listing 14.1. Wyświetlanie atrybutów pliku

<!DOCTYPE html>
<html><head>
<meta charset="UTF-8" />
<title>14.1. Odczyt atrybutów pliku</title>
<script>
// obsługa wybranego pliku
function handleFile(fileInput) {

// odwołanie do elementu section, w którym pokazujemy atrybuty


var fileAttributes = document.getElementById('fileAttributes');

// sprawdź, czy przeglądarka wspiera interfejs obsługujący pliki


if (fileInput.files) {

// odwołanie do pliku przy użyciu interfejsu


var file = fileInput.files[0];
Odczyt atrybutów pliku 333

// utwórz wynik
var output = 'Atrybuty pliku:<br>';
output += 'nazwa: ' + file.name + '<br>';
output += 'typ: ' + file.type + '<br>';
output += 'rozmiar: ' + (file.size/1024).toFixed(2) + 'KB<br>';
output += 'data ostatniej modyfikacji: ' + file.lastModifiedDate;

// wstaw wynik do elementu section


fileAttributes.innerHTML = output;

} else {
// interfejs obsługujący pliki nie jest dostępny w przeglądarce
fileAttributes.innerHTML = 'Interfejs obsługujący pliki nie jest dostępny';
}
}
</script>
</head>
<body>
<h1>Wybierz plik:</h1>
<section>
<input type="file" id="input" onchange="handleFile(this)" />
</section>
<br>
<section id="fileAttributes"></section>
</body>
</html>

Kiedy odwiedzający stronę utworzoną na podstawie listingu 14.1 wybierze jakiś plik, używając służącego
do tego przycisku, procedura obsługi zdarzenia onchange wywoła funkcję handleFile z elementem input
typu file jako parametrem. Wewnątrz tej funkcji najpierw pobierane jest odwołanie do elementu section,
służącego do pokazania wyników, po czym następuje weryfikacja, czy przeglądarka obsługuje interfejs
FileList, poprzez sprawdzenie obecności tablicy files. Jeśli przeglądarka nie obsługuje tego interfejsu,
w elemencie section o atrybucie fileAttributes jest wyświetlany odpowiedni komunikat, ale na taką sytuację
możesz zareagować według własnego pomysłu albo poprosić odwiedzającego o więcej informacji.
Jeśli interfejs FileList jest obsługiwany, za pomocą odwołania do pierwszego pliku w tablicy files
(fileInput.files[0]) jest pobierana instancja typu File, która następnie zostaje użyta do odczytania
właściwości name, type, size oraz lastModifiedDate. Ponieważ jednostką miary atrybutu size jest bajt,
a bardziej powszechne jest podawanie rozmiaru plików w kilobajtach, trzeba przeprowadzić odpowiednią
konwersję za pomocą wyrażenia, które przekształca bajty na kilobajty. Możesz poprawić to wyrażenie,
tworząc funkcję, która pobierze liczbę bajtów i pokaże konwersję na kilobajty, megabajty albo inną jednostkę,
zależnie od całkowitej liczby bajtów, tak jak jest to robione w zwykłych listach plików w katalogach.

Uwaga
Interfejs File dziedziczy z interfejsu Blob, w związku z czym zawiera atrybuty size oraz type. Atrybuty
name i lastModifiedDate są dodane w interfejsie File. Jednak w kilku przeglądarkach lastModifiedDate
nie jest dostępny i poproszony o wartość będzie zwracał undefined.

W tym przepisie pokazaliśmy atrybuty pliku wybranego przez odwiedzającego. Przydaje się to do
filtrowania plików przed przesłaniem do serwerów albo po prostu do pokazania użytkownikowi jakiegoś
komunikatu. W następnym przepisie rozwiniemy ten temat, udostępniając odwiedzającemu interfejs
pozwalający na wybranie wielu plików do przetworzenia.
334 Rozdział 14. Praca z plikami lokalnymi

PRZEPIS DLA POCZĄTKUJĄCYCH


Przetwarzanie wielu plików za pomocą
przeciągania i upuszczania
Interfejs FileList udostępnia możliwość obsługiwania tablicy obiektów File reprezentujących pliki z lokalnego
systemu plików. Ten przepis pokazuje, jak można użyć obszaru przeciągania i upuszczania oraz interfejsu
FileList do obsługi wielu plików wybranych przez użytkownika, co może mieć wiele zastosowań, takich jak
filtrowanie plików według nazwy, rozmiaru czy typu oraz potwierdzanie przez użytkownika wyboru plików.
Po przeciągnięciu i upuszczeniu plików na strefę upuszczania skrypt wykorzysta właściwość length i przejdzie
w pętli przez sekwencję FileList. Właściwość length zwraca liczbę elementów albo obiektów File w FileList
i może być użyta do „przejścia” przez sekwencję plików. Dla każdego obiektu File strona wyświetli atrybuty
pliku w tabeli, aby poinformować użytkownika, co zostało upuszczone na strefę upuszczania strony. Aby
utworzyć stronę, wykonaj następujące kroki oraz skorzystaj z kodu z listingu 14.2:
1. Utwórz stronę z listingu 14.2, zawierającą znaczniki style i body, strefę upuszczania i element
section o identyfikatorze filesSelected. Upewnij się, że element input ma atrybut multiple
oraz procedurę obsługi zdarzenia onchange.
2. W funkcji init dodaj procedury nasłuchu zdarzeń strefy upuszczania. Dodaj funkcje
handleFileDrop oraz handleDragOver, służące do aktywacji tej strefy.
3. Dodaj funkcję displayFiles służącą do przejścia w pętli przez wybrane pliki i wyświetlania
atrybutów w tabeli.
4. Dodaj funkcję clearTable oraz wyzwalacz addEventListener zdarzenia load.

Listing 14.2. Przechodzenie w pętli przez wiele plików za pomocą FileList

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>14.2. Przetwarzanie wielu plików za pomocą FileList</title>
<style>
#dropZone {
width:300px;
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 25px;
text-align: center;
font: 20pt bold;
color: #bbb;
}
#fileTable {
border: 1px solid #000;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 5px;
visibility:hidden;
}
tr:nth-child(odd) { background-color:#eee; }
tr:nth-child(even) { background-color:#fff; }
Przetwarzanie wielu plików za pomocą przeciągania i upuszczania 335

</style>
<script>

// inicjalizuj strefę upuszczania


function init() {

// konfiguruj procedury nasłuchu strefy upuszczania


dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileDrop, false);
}

// obsługa upuszczonych plików


function handleFileDrop(evt) {

// zatrzymaj przeciąganie i upuszczanie


evt.stopPropagation();
evt.preventDefault();

// pobierz listę plików


var files = evt.dataTransfer.files;

// wyświetl wybraną listę plików


displayFiles(files);

// obsługa plików przeciąganych ponad strefą


function handleDragOver(evt) {

// wyłącz strefę upuszczania w trakcie przeciągania nad nią pliku


evt.stopPropagation();
evt.preventDefault();
}

// wyświetl wybrane pliki


function displayFiles(files) {

// wyczyść bieżącą tabelę


clearTable();

// wyświetl liczbę wybranych plików


var fileCount = document.getElementById('fileCount');
fileCount.innerHTML = 'Liczba wybranych plików: ' + files.length;

// ustaw pola do generowania tabeli


var fileTable = document.getElementById('fileTable');

if (files.length>0) {
var row;
var cell;
var textNode;

// przejdź w pętli przez listę plików i utwórz wiersze


for (var i=0; i<files.length; i++) {

// dodaj wiersz
var row = fileTable.insertRow(i);

// dodaj komórkę na nazwę pliku


cell = row.insertCell(0);
336 Rozdział 14. Praca z plikami lokalnymi

textNode = document.createTextNode(files[i].name);
cell.appendChild(textNode);

// dodaj komórkę na typ pliku


cell = row.insertCell(1);
textNode = document.createTextNode(files[i].type);
cell.appendChild(textNode);

// dodaj komórkę na rozmiar pliku


cell = row.insertCell(2);
textNode = document.createTextNode((files[i].size/1024).toFixed(2)+'KB');
cell.appendChild(textNode);

if (files[i].lastModifiedDate != undefined) {
// dodaj komórkę na datę ostatniej modyfikacji pliku
cell = row.insertCell(3);
textNode = document.createTextNode(files[i].lastModifiedDate);
cell.appendChild(textNode);
}
}
fileTable.style.visibility = 'visible';
} else {
fileTable.style.visibility = 'hidden';
}
}

// wyczyść tabelę
function clearTable() {

// uzyskaj odwołanie do tabeli


var fileTable = document.getElementById('fileTable');

// przejdź w pętli przez znajdujące się w tabeli wiersze i usuwaj je


while (fileTable.rows.length>0) {
fileTable.deleteRow(fileTable.rows.length-1);
}
}

// inicjalizuj okno
window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Przeciągnij kilka plików i upuść na poniższą strefę:</h1>
<section id="fileSelection">
<!--
// Wybór wielu plików można również osiągnąć za pomocą atrybutu multiple
<input type="file" id="input" multiple="true" onchange="displayFiles(this.files)">
-->
<div id="dropZone">Tutaj upuszczaj pliki</div>
</section>
<section id="filesSelected">
<br>
<div id="fileCount"></div>
<table id="fileTable">
</table>
</section>
</body>
</html>
Interfejs FileReader 337

Po załadowaniu strony przeglądarka inicjalizuje strefę upuszczania za pomocą wywołania funkcji init
przez procedurę nasłuchu zdarzenia load okna. Funkcja init ustawia zmienną dropzone na element div
o identyfikatorze dropZone oraz rejestruje dwie procedury nasłuchu dla zdarzeń dragover i drop. Po aktywowaniu
zdarzenia dragover przeglądarka wywołuje funkcję handleDragOver, a po aktywowaniu zdarzenia drop
— funkcję handleFileDrop. Po upuszczeniu plików na strefie upuszczania i wywołaniu handleFileDrop
skrypt pobierze listę plików i przekaże ją metodzie displayFiles.
Funkcja displayFiles wywołuje funkcję pomocniczą clearTable, by wyczyścić wszystkie poprzednie wyniki.
clearTable usuwa po kolei wszystkie wiersze z tabeli. Następnie displayFiles sprawdza długość FileList,
aby dowiedzieć się, czy na strefę upuszczania zostały upuszczone jakieś pliki. Skrypt przechodzi w pętli przez
elementy tablicy, korzystając z indeksu do odczytu każdego interfejsu File. Funkcja pobiera i do nowego
wiersza tabeli dodaje jako nową komórkę atrybuty każdego obiektu File. Wreszcie, skrypt wyświetla tabelę
poprzez spowodowanie, że staje się widoczna, jak pokazano w przykładowym wykonaniu na rysunku 14.1.

Rysunek 14.1. Przykładowy wynik pokazujący wiele atrybutów z przeciągniętych


i upuszczonych na stronę plików

Wykorzystując strefę upuszczania i sekwencję FileList, HTML5 ułatwia odwiedzającym przesyłanie


plików na stronę klienta. Do tego momentu odczytywaliśmy atrybuty plików, a nie dane z pliku. W pozostałej
części tego rozdziału pokażemy, jak odczytywać różne rodzaje plików w JavaScripcie.

Wskazówka
Atrybut type interfejsu File, który jest dziedziczony z interfejsu Blob, zwraca typ pliku MIME (ang.
Multipurpose Internet Mail Extensions). Typ MIME jest typu DOMString, w którym wszystkie litery są
małe i który jest zapisany przy użyciu kodowania ASCII. Ciąg tekstowy typu MIME może być używany
do filtrowania rodzajów plików, na przykład obrazków (image) lub innych. Mimo że na rysunku 14.1
zaprezentowano dwa obrazki różnych rodzajów, możesz łatwo stwierdzić, że oba pliki są obrazkami,
gdyż MIME zaczyna się od słowa image. Typ MIME udostępnia prosty i skuteczny sposób filtrowania
i sprawdzania plików, co pokażemy w następnym podrozdziale.

Interfejs FileReader
Specyfikacja HTML5 File API udostępnia nowy interfejs o nazwie FileReader, służący do wczytania plików
do pamięci klienta. Interfejs ten udostępnia metody, atrybuty i zdarzenia, które pozwalają programistom
odczytywać asynchronicznie pliki z JavaScriptu po stronie klienta. Poprzez zdarzenia tego interfejsu możesz
338 Rozdział 14. Praca z plikami lokalnymi

wyświetlać albo przetwarzać dane z plików. Ponieważ interfejs FileReader posiada trzy różne metody służące
do odczytu, umożliwia wczytywanie wielu różnych rodzajów plików:
„ readAsArrayBuffer(Blob) — zwraca zawartość pliku jako ArrayBuffer,
„ readAsText(Blob [, encoding]) — zwraca zawartość pliku jako tekst DOMString,
„ readAsDataURL(Blob) — zwraca zawartość pliku jako DOMString w postaci danych URL.

Ponieważ interfejs FileReader jest asynchroniczny, główny wątek strony może kontynuować przetwarzanie,
a do przechwytywania kluczowych etapów odczytu pliku są używane zdarzenia. Tabela 14.2 pokazuje zdarzenia
zdefiniowane w ramach interfejsu FileReader.

Tabela 14.2. Zdarzenia FileReader

Nazwa zdarzenia Atrybut Cel


loadstart onloadstart Wyzwalane, gdy rozpoczyna się odczyt pliku.
progress onprogress Wyzwalane przez przeglądarkę w wybranych przez nią momentach
podczas odczytu pliku.
abort onabort Wyzwalane przy przerwaniu odczytu.
error onerror Wyzwalane w razie napotkania błędu podczas odczytu pliku.
load onload Wyzwalane, gdy pomyślnie odczytano plik.
loadend onloadend Wyzwalane, gdy odczyt się zakończył, pomyślnie lub błędnie.

Metody służące do odczytu treści pliku znajdujące się w interfejsie FileReader oraz zdarzenia z tego
interfejsu mogą posłużyć do wykonania w przeglądarce klienta działań, które byłyby zwykle wykonywane
na serwerze po przesłaniu tam pliku. W następnym przepisie użyjemy metody readAsDataURL do utworzenia
miniatur wybranych obrazków w przeglądarce użytkownika i pokazania ich atrybutów.

Uwaga
Pliki mogą być także odczytywane za pomocą interfejsu synchronicznego FileReaderSync wraz z wątkiem
roboczym, gdyż Web Workers API pozwala metodom działać w innym wątku niż wątek główny. Interfejs
FileReaderSync ma te same metody co FileReader. Aby uzyskać więcej informacji o wykorzystaniu File
API w sposób synchroniczny, zajrzyj do W3C Working Draft.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Podgląd obrazków przy użyciu readAsDataURL
Metoda readAsDataURL interfejsu FileReader pobiera dostarczone odwołanie do obiektu Blob albo File
i odczytuje plik w postaci danych URL, które mogą być załadowane do odpowiedniego pojemnika. W tym
przepisie dasz odwiedzającemu możliwość wyboru wielu plików, odfiltrujesz spośród nich obrazki, odczytasz
je za pomocą readAsDataURL i załadujesz do pojemników w celu wyświetlenia jako miniatury. Zanim
pojawił się HTML5 File API, aby odtworzyć taką funkcjonalność, musiałeś załadować pliki na serwer,
tam je przetworzyć, a następnie załadować je na stronę przeglądarki klienta. Nakładało to konieczność
wysyłania plików tam i z powrotem i przetwarzania ich na serwerze, co powodowało większe wykorzystanie
Podgląd obrazków przy użyciu readAsDataURL 339

dostępnych zasobów. Korzystając z HTML5 File API, nie musisz wysyłać plików w tę i z powrotem i możesz
wykorzystać moc obliczeniową komputera klienta do przetworzenia plików.
Ten przepis wykorzysta metodę readAsDataURL wraz ze zdarzeniem onload interfejsu FileReader do
odczytania wybranych plików. Ponadto przepis wyświetli atrybuty każdego pliku po najechaniu na jego
miniaturkę kursorem. Aby rozpocząć korzystanie z tego przepisu, wykonaj następujące kroki i utwórz
listing 14.3:
1. Utwórz pusty plik HTML i dodaj HTML-owy znacznik body z listingu 14.3, zawierający element
input typu file z atrybutem multiple oraz elementy służące do wyświetlania miniatur i atrybutów.
2. Dodaj element style zawierający trzy zbiory stylów do obsługi zaznaczonych i niezaznaczonych
obrazków.
3. Dodaj w elemencie script funkcję handleFiles, pokazaną na listingu 14.3, która jest uruchamiana
po wybraniu plików.
4. Dodaj funkcję showFile pokazującą atrybuty pliku, nad którym znajduje się kursor.
5. Dodaj funkcję clearFile, która po prostu czyści obszar atrybutów, kiedy kursor nie znajduje
się już nad obrazkiem.

Listing 14.3. Podgląd obrazków z wykorzystaniem readAsDataURL

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>14.3. FileReader — panel z obrazkami lokalnymi</title>
<style>
.highlight,.unhighlight {
max-height:100px;
max-width:100px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
margin:10px;
}
.highlight {
border: 5px solid #6f0;
}
.unhighlight {
border: 5px solid #000;
}
</style>
<script>

// załaduj wybrane pliki obrazków


function handleFiles(files) {

// ustawienia domyślne
var fileLimit = 10; // maksymalna liczba plików przetwarzanych jednocześnie
var sizeLimit = 500; // ograniczenie rozmiaru plików w KB
var imageType = /image.*/; // typ MIME obrazków — wyrażenie regularne

// odwołaj się do panelu obrazków i wyczyść go


var imgPanel = document.getElementById('imgPanel');
340 Rozdział 14. Praca z plikami lokalnymi

imgPanel.innerHTML = '';

// oblicz ograniczenie rozmiaru plików w bajtach


var sizeLimitBytes = sizeLimit*1024;

// sprawdź, czy liczba plików jest mniejsza od ograniczenia


if (files.length<fileLimit) {

// przejdź w pętli przez listę plików


for (var i = 0; i < files.length; i++) {

// odwołanie do bieżącego pliku


var file = files[i];

// sprawdź, czy plik jest obrazkiem


if (file.type.match(imageType)) {

// sprawdź, czy plik nie przekracza maksymalnego rozmiaru


if (file.size<sizeLimitBytes) {

// utwórz pojemnik obrazka z pliku


var img = document.createElement("img");
img.file = file;
img.className = 'unhighlight';
img.addEventListener('mouseover', showFile, false);
img.addEventListener('mouseout', clearFile, false);
imgPanel.appendChild(img);

// utwórz obiekt reader do odczytu pliku


var reader = new FileReader();
// ustaw zdarzenie onload obiektu reader
reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);

// przeczytaj plik obrazka i zapisz w formacie data url


reader.readAsDataURL(file);

} else {
// plik jest za duży
alert(file.name+' ma rozmiar większy niż '+sizeLimit+'KB.');
}
} else {
// plik nie jest obrazkiem
alert(file.name+' nie jest obrazkiem.');
}
}
} else {
// wybrano zbyt wiele plików
imgPanel.innerHTML = 'Wybrano zbyt wiele plików. Maksymalna dozwolona liczba plików to '+fileLimit+'.';
}
}

// wyświetl informację na temat obrazka, nad którym znajduje się kursor myszy
function showFile() {

// podświetl bieżący obrazek


this.className = 'highlight';

// pobierz odwołanie do panelu atrybutów i do pliku


var fileAttributes = document.getElementById('fileAttributes');
Podgląd obrazków przy użyciu readAsDataURL 341

var file = this.file;

// utwórz informację o pliku


var fileinfo = 'Informacja o pliku:<br>';
fileinfo += file.name + '<br>';
fileinfo += file.type + '<br>';
fileinfo += (file.size/1024).toFixed(2) + 'KB<br>';
fileinfo += file.lastModifiedDate + '<br>';

// wyświetl informację o pliku


fileAttributes.innerHTML = fileinfo;

// wyczyść po wyjściu kursora myszy znad obrazka


function clearFile() {

// wyczyść panel atrybutów pliku


var fileAttributes = document.getElementById('fileAttributes');
fileAttributes.innerHTML = '';

// ustaw podświetlenie obrazka z powrotem na normalne


this.className = 'unhighlight';
}

</script>
</head>
<body>
<h1>Wybierz obrazki do podglądu.</h1>
<section>
<input type="file" id="input" multiple="true" onchange="handleFiles(this.files)">
</section>
<section id="imgPanel"></section>
<section id="fileAttributes"></section>
</body>
</html>

Po załadowaniu strony utworzonej na podstawie listingu 14.3 prosi ona odwiedzającego o wybranie pliku
lub zbioru plików. Następnie przeglądarka aktywuje zdarzenie onchange elementu input. Główna funkcja
skryptu — handleFiles — ustawia najpierw najważniejsze wartości domyślne: maksymalną dozwoloną liczbę
plików, maksymalny rozmiar każdego pliku oraz wyrażenie regularne, które będzie używane do dopasowywania
typów MIME obrazków. Rozmiar pliku i liczba plików są ograniczone, aby uniknąć przeciążania pamięci
przeglądarki, gdyż przetwarzanie będzie wykonywane w pamięci komputera klienta. W tym przepisie skrypt
przejdzie w pętli przez pliki i utworzy wiele działających w tym samym czasie instancji interfejsu FileReader.
Skrypt mógłby być bardziej inteligentny, jeśli chodzi o przetwarzanie, i posiadać zbiór obiektów odczytujących,
które nie pobierałyby kolejnych plików do momentu zwolnienia jakiegoś obiektu odczytującego, ale ewentualną
implementację tego mechanizmu zostawiamy Tobie.
Skrypt sprawdza, czy liczba wybranych plików nie przekracza maksymalnej, a jeśli przekracza, wyświetla
odpowiedni komunikat. Jeśli wybrana jest odpowiednia liczba plików, wtedy przechodzi w pętli przez listę
plików (FileList) i sprawdza, czy każdy plik jest obrazkiem, poprzez dopasowywanie typu MIME oraz
upewnia się, że rozmiar każdego z plików jest mniejszy od maksimum, które ustanowiłeś. Jeśli każdy plik
spełnia te wymagania, przepis tworzy pojemnik obrazków z właściwościami dotyczącymi zdarzeń mouseover
i mouseout w celu pokazywania atrybutów. Teraz skrypt jest gotowy do odczytu pliku poprzez utworzenie
342 Rozdział 14. Praca z plikami lokalnymi

nowego obiektu FileReader dla każdego obrazka. Skrypt ustawia na obiekcie FileReader zdarzenie onload,
służące do załadowania wyniku (result) obiektu FileReader do atrybutu src elementu obrazka, co spowoduje
utworzenie miniatury, jak pokazano na rysunku 14.2.

Rysunek 14.2. Przykładowy wynik pokazujący wybrane przez użytkownika pliki obrazków
wyświetlone za pomocą metody readAsDataURL

Na koniec obiekt FileReader odczytuje każdy plik metodą readAsDataURL, która zwróci wynik w postaci
danych URL do ustawienia jako źródło obrazka. Pozostała część funkcji obsługuje błędy związane z różnymi
testami umieszczonymi w funkcji i wyświetla użytkownikowi odpowiednie komunikaty. Funkcje showFile
i clearFile wyświetlają atrybuty plików.
Przepis ten pokazuje prosty sposób użycia interfejsu FileReader i jednej z dostępnych metod odczytu. Poza
tym mamy nadzieję, że ten przepis pokazał Ci, iż musisz uważać przy uruchamianiu wielu asynchronicznych
odczytów jednocześnie w przeglądarce klienta. Aby się przekonać, jak ważne jest to ograniczenie, powiększ
limity dotyczące rozmiaru pliku i liczby plików (ale zrób to ostrożnie, ponieważ łatwo osiągnąć maksimum
pamięci dostępnej w przeglądarce). W następnym przepisie poznasz pewne dodatkowe funkcje interfejsu
FileReader, które pozwolą Ci zatrzymać przetwarzanie i przechwycić mogące wystąpić błędy.

PRZEPIS DLA ZAAWANSOWANYCH


Parsowanie pliku CSV za pomocą readAsText
Możliwość przetwarzania plików w przeglądarce za pomocą interfejsu FileReader powinna Cię ucieszyć.
Dzięki temu możesz przenieść przetwarzanie, które wcześniej było problematyczne, na stronę klienta. Jednym
z typowych zastosowań jest import plików, takich jak dokumenty z danymi w formie wartości oddzielonych
przecinkami (CSV). Zwykle plik jest przesyłany na serwer, a następnie parsowany albo natychmiast, albo
według ustalonego harmonogramu, po czym jest importowany do bazy danych w celu wykorzystania w witrynie
lub aplikacji. Proces ten zużywa cenne cykle serwera.
W tym przepisie pokażemy Ci alternatywne rozwiązanie dostępne w HTML5, wykorzystujące metodę
readAsText. Przepis pozwoli użytkownikowi wybrać lokalny plik CSV, a przeglądarka odczyta go i sparsuje,
a następnie pokaże jego wiersze i pola. Zauważ jednak, że algorytm parsowania CSV w tym przepisie nie
zajmuje się wszystkimi zawiłościami struktury CSV. W internecie jest wiele miejsc, w których możesz znaleźć
solidne algorytmy parsujące pliki CSV. Celem tego przepisu jest raczej pokazanie sposobu użycia interfejsu
FileReader i metody readAsText. Po sparsowaniu wartości pól mogą zostać umieszczone w ciągu tekstowym
w formacie JSON i wysłane poprzez ajaksowe wywołanie usługi sieciowej do serwera w celu przetwarzania.
Parsowanie pliku CSV za pomocą readAsText 343

Poza nową metodą — readAsText — w przepisie wykorzystamy również funkcję interfejsu FileReader
o nazwie abort. Ta metoda pozwala skryptowi albo użytkownikowi zatrzymać proces odczytu. Po poprawnym
wywołaniu metody abort FileReader generuje ABORT_ERR, aby potwierdzić, że nastąpiło zatrzymanie. Interfejs
FileReader ma pięć możliwych błędów, które mogą być wygenerowane z powodu problemów z odczytem
podanego pliku albo obiektu Blob. Pokazuje je tabela 14.3.

Tabela 14.3. Możliwe błędy interfejsu FileReader

Stała błędu Wartość Wyjaśnienie


NOT_FOUND_ERR 1 Plik do odczytu nie został znaleziony.
SECURITY_ERR 2 Plik uległ zmianie podczas odczytywania, plik jest uznany za niebezpieczny
albo występuje zbyt dużo odczytów pliku.
ABORT_ERR 3 Została wywołana metoda abort.
NOT_READABLE_ERR 4 Nie można odczytać pliku. Zazwyczaj jest to spowodowane brakiem
uprawnień.
ENCODING_ERR 5 Wynik wywołania metody readAsDataURL nie ma formatu danych URL.
Nie dotyczy to problemów z kodowaniem przy odczycie pliku jako tekstu.

W tym przepisie zgłębisz jedną z ostatnich procedur obsługi zdarzeń udostępnianą przez FileReader
— onprogress. Procedura obsługi zdarzenia onprogress udostępnia powiadomienia dotyczące postępów
odczytu pliku i może być używana do wyświetlania użytkownikowi statusu odczytu. Procedura obsługi
zdarzenia onprogress zawiera trzy atrybuty: lengthComputable, loaded i total. Atrybut lengthComputable
jest flagą logiczną, która mówi, czy atrybuty loaded i total są dostępne. Atrybut loaded jest liczbą bajtów
wczytanych dotychczas do pamięci, podczas gdy total jest całkowitą liczbą bajtów do odczytania. Dzieląc
progress.loaded przez progress.total, możesz uzyskać procent wykonania zadania, który może być
wyświetlony użytkownikowi. W tym przepisie w miarę wczytywania pliku do pamięci strona będzie pokazywać
pasek postępu ze statusem procesu odczytu. Zauważ jednak, że wyzwalanie zdarzenia progress zależy od
implementacji interfejsu FileReader w przeglądarce, więc nie ma pewności co do tego, kiedy albo jak często
zdarzenie to będzie generowane. Następujące kroki pozwolą Ci utworzyć kod z listingu 14.4:
1. Utwórz pustą stronę HTML ze znacznikami body i style, takimi jak pokazane na listingu 14.4,
zawierającymi przycisk wyboru pliku, przycisk anulowania, blok informacji o pliku i element
div przeznaczony na dane z pliku.
2. Dodaj znaczniki style i nadaj style różnym elementom, między innymi do ukrywania przycisku
anulowania.
3. Dodaj znaczniki script i globalne odwołanie do instancji FileReader o nazwie textReader.
Będzie to instancja FileReader służąca do odczytu wybranego pliku CSV.
4. Dodaj wiersz zawierający wywołanie addEventListener dotyczące zdarzenia load i funkcji init,
która rejestruje funkcje obsługi zdarzeń obiektu textReader.
5. Dodaj odpowiednie funkcje zdarzeń: onErrorHandler, updateProgress, onAbortHandler,
onLoadStartHandler i onLoadHandler.
6. Dodaj funkcję cancelFileReader, która umożliwia odwiedzającemu zatrzymanie procesu odczytu.
7. Dodaj funkcję handleFile, która uruchamia proces odczytu wybranego pliku za pomocą metody
readAsText interfejsu FileReader.
344 Rozdział 14. Praca z plikami lokalnymi

Listing 14.4. Wyświetlanie zawartości pliku CSV

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>14.4. Podstawowe File API</title>
<style>
#fileInfo {
border: 1px solid #000;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 5px;
visibility:hidden;
}
#progHolder {
float:right;
width:200px;
height:30px;
border:solid;
border-width:1px;
background-color:#999;
text-align:center;
}
#progMeter {
width:0px;
height:30px;
background-color:#9FF;
}
#btnCancel {
visibility:hidden;
}
</style>
<script>
// utwórz obiekt FileReader
var textReader = new FileReader();

function init() {
// skonfiguruj procedury obsługi obiektu FileReader
textReader.onerror = onErrorHandler;
textReader.onprogress = updateProgress;
textReader.onabort = onAbortHandler;
textReader.onloadstart = onLoadStartHandler;
textReader.onload = onLoadHandler;
}

// obsługa wybranego pliku


function handleFile(inputFile) {

// wyrażenie regularne dla analizy typu MIME


var csvMimeType = /text\/csv|application\/vnd\.ms-excel/;

// odwołanie do wybranego pliku


var file = inputFile.files[0];

// sprawdź, czy plik jest typu csv


if (file.type.match(csvMimeType)) {
Parsowanie pliku CSV za pomocą readAsText 345

// spraw, aby blok informacji o pliku był widoczny


var fileInfo = document.getElementById("fileInfo");
fileInfo.style.visibility = 'visible';

// uruchom odczyt pliku csv za pomocą obiektu FileReader


textReader.readAsText(file);

// ustaw informacje o pliku — FileReader jest asynchroniczny


// wyświetlone zostaną poniższe informacje
var output = 'Atrybuty pliku:<br>';
output += 'nazwa: ' + file.name + '<br>';
output += 'typ: ' + file.type + '<br>';
output += 'rozmiar: ' + (file.size/1024).toFixed(2) + ' KB<br>';
output += 'data ostatniej modyfikacji: ' + file.lastModifiedDate;

// wyświetl atrybuty pliku


var fileAttributes = document.getElementById('fileAttributes');
fileAttributes.innerHTML = output;

} else {
// poinformuj użytkownika, że plik nie jest typu csv
alert(file.name + ' nie jest plikiem CSV.');
}
}

// funkcja anulowania odczytu


function cancelFileReader() {

// nakaż obiektowi FileReader przerwać działanie


textReader.abort();
}

// obsługa aktywowanego zdarzenia onloadstart obiektu FileReader


function onLoadStartHandler(evt) {

// pobierz referencje do elementów lokalnych


var btnCancel = document.getElementById('btnCancel');
var progMeter = document.getElementById('progMeter');
var fileoutput = document.getElementById('fileoutput');

// ustaw domyślne wartości przycisku anulowania, wskaźnika postępu i wyników z pliku


btnCancel.style.visibility = 'visible';
progMeter.style.width = '0%';
progMeter.innerHTML = 'trwa ładowanie pliku...';
fileoutput.innerHTML = '';
}

// obsługa aktywowanego zdarzenia onload obiektu FileReader


function onLoadHandler(evt) {

// ustaw wskaźnik postępu na 100% i ukryj przycisk anulowania


var progMeter = document.getElementById('progMeter');
var btnCancel = document.getElementById('btnCancel');
progMeter.style.width = '100%';
progMeter.innerHTML = 'Plik został załadowany.';
btnCancel.style.visibility = 'hidden';

// rozbij treść pliku na wiersze i zapisz je w tablicy w celu przetwarzania


var fileArr = evt.target.result.split('\n');
346 Rozdział 14. Praca z plikami lokalnymi

// przetwarzaj każdy wiersz i ustaw wiersze w tabeli


// to jest uproszczone przetwarzanie formatu csv
// i nie obsługuje różnic w tym formacie
var strDiv = '<table>';
for (var i=0; i<fileArr.length; i++) {
strDiv += '<tr>';
var fileLine = fileArr[i].split(',');
for (var j=0; j<fileLine.length; j++) {
strDiv += '<td>'+fileLine[j].trim()+'</td>';
}
strDiv += '</tr>';
}
strDiv += '</table>';

// ustaw wynik
var fileoutput = document.getElementById('fileoutput');
fileoutput.innerHTML = strDiv;

// obsługa postępu dla obiektu FileReader


function updateProgress(evt) {

// odwołanie do wskaźnika postępu


var progMeter = document.getElementById('progMeter');

// oblicz i wyświetl postęp


if (evt.lengthComputable) {
var loaded = Math.round((evt.loaded / evt.total)*100);
if (loaded < 100) {
progMeter.style.width = loaded + '%';
} else {
progMeter.style.width = '100%';
}
}
}

// obsługa zatrzymania odczytu poprzez FileReader


function onAbortHandler(evt) {
alert('Odczyt pliku anulowany');
}

// obsługa błędów dla obiektu FileReader


function onErrorHandler(evt) {
switch(evt.target.error.code) {
case evt.target.error.NOT_FOUND_ERR:
alert('Nie znaleziono pliku!');
break;
case evt.target.error.SECURITY_ERR:
alert('Błąd bezpieczeństwa pliku.');
break;
case evt.target.error.ABORT_ERR:
break;
case evt.target.error.NOT_READABLE_ERR:
alert('Nie można odczytać pliku.');
break;
case evt.target.error.ENCODING_ERR:
alert('Błąd kodowania pliku.');
break;
Parsowanie pliku CSV za pomocą readAsText 347

default:
alert('Wystąpił błąd przy odczycie pliku.');
};
}

// inicjalizuj okno
window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Wybierz plik CSV do przetworzenia</h1>
<section>
<input type="file" id="input" onchange="handleFile(this)">
<button id="btnCancel" onclick="cancelFileReader();">Anuluj przetwarzanie</button>
</section>
<br>
<section id="fileInfo">
<div id="progHolder">
<div id="progMeter"></div>
</div>
<div id="fileAttributes"></div>
</section>
<br>
<div id="fileoutput"></div>
</body>
</html>

Po załadowaniu strony do przeglądarki odwiedzający może wybrać plik CSV do wczytania. Po wybraniu
pliku przeglądarka wywołuje funkcję handleFile, przekazując jej plik. Funkcja ta sprawdza najpierw, czy
wybrany plik jest typu CSV, dopasowując wyrażenie regularne do typu MIME. Jeśli typ MIME pliku nie
pasuje do wyrażenia, wtedy przeglądarka wyświetla powiadomienie informujące użytkownika, że plik nie
ma formatu CSV. Jeśli plik ma format CSV, wtedy kod wywołuje metodę readAsText instancji FileReader
z plikiem przekazanym jako parametr. Rozpoczyna to proces odczytu w sposób asynchroniczny, który
powoduje wywołanie odpowiednich zdarzeń w miarę postępów odczytu. Aby potwierdzić, że odczyt jest
asynchroniczny, po wywołaniu funkcji readAsText umieściliśmy wyświetlanie atrybutów pliku. Przeglądarka
pokaże atrybuty zaraz po wywołaniu readAsText, gdyż odczyt nie blokuje głównego wątku skryptu.
W funkcji init w skrypcie zarejestrowaliśmy funkcję onLoadStartHandler, wywoływaną, kiedy wystąpi
zdarzenie onloadstart, które zostanie aktywowane, kiedy FileReader rozpocznie odczytywanie pliku. Gdy
to zdarzenie wystąpi, onLoadStartHandler wyświetli przycisk anulowania, wskaźnik postępu i wynikowy
element div.
W czasie odczytywania wskaźnik postępu jest aktualizowany poprzez metodę updateProgress, wywoływaną,
kiedy FileReader aktywuje zdarzenie onprogress. Metoda updateProgress sprawdza, czy dostępne są atrybuty
loaded i total, sprawdzając atrybut lengthComputable. Jeśli atrybut ten ma wartość true, oznacza to, że
atrybuty loaded i total są dostępne. updateProgress wykorzystuje je do obliczenia procentowego ukończenia
zadania i wyświetlenia tej wartości za pomocą wskaźnika postępu, który jest budowany poprzez ustawienie
szerokości elementu div o identyfikatorze progMeter w elemencie div o identyfikatorze progHolder.
Po zakończeniu odczytywania pliku przez obiekt FileReader przeglądarka aktywuje zdarzenie onload
i wywołuje funkcję onLoadHandler, przekazując jej wyniki w zdarzeniu. W tej metodzie wyłączyliśmy przycisk
anulowania i ustawiliśmy wartość postępu na 100 procent, tak aby odwiedzający wiedział, że plik został
348 Rozdział 14. Praca z plikami lokalnymi

wczytany do pamięci. Ponieważ pliki CSV mają dane ułożone w wierszach, dzielimy następnie wynik według
znaku przejścia do nowego wiersza, tworząc tablicę wierszy za pomocą następującego polecenia:
var fileArr = evt.target.result.split('\n');

Odwołanie result przechowuje tekstową zawartość pliku w pamięci. Po podzieleniu wyniku na tablicę,
której wiersze zawierają wartości rozdzielone przecinkami, skrypt przechodzi w pętli przez każdy element
tablicy i dzieli wartości według znaku przecinka. Przechodząc przez każdy wiersz, skrypt dodaje wartości
do wynikowego ciągu tekstowego, który zostaje wyświetlony w elemencie div przeznaczonym na wyniki
odczytania pliku, kiedy wszystkie wiersze są przetworzone, jak pokazano na rysunku 14.3.

Rysunek 14.3. Przykładowy wynik pokazujący wybrany i sparsowany plik CSV

Sposób parsowania pliku z wartościami oddzielonymi przecinkami użyty w tym przepisie jest dość prosty.
Nie obsługuje przypadków takich jak przecinki występujące w wartościach i nie rozumie różnicy między
wartościami zawartymi i niezawartymi w cudzysłowach. W internecie możesz znaleźć bardziej rozbudowane
sposoby parsowania, lecz w celu pokazania, jak działa metoda readAsText, użyliśmy w przepisie prostego
modelu.
W przepisie zawarliśmy możliwość zatrzymania odczytu za pomocą przycisku anulowania. Jeśli odwiedzający
kliknie przycisk anulowania, kiedy FileReader odczytuje plik, przeglądarka wywoła metodę cancelFileReader,
która z kolei wywoła metodę abort na instancji FileReader — textReader. Instruuje ona instancję FileReader,
aby anulować proces odczytu, usuwa z pamięci wszystkie już odczytane informacje i aktywuje zdarzenie
onabort. Skrypt przechwytuje z kolei zdarzenie onabort za pomocą metody onaborthandler i wyświetla
odwiedzającemu komunikat potwierdzający anulowanie odczytu.
Może zauważyłeś, że skrypt zawiera procedurę obsługi wszystkich błędów przekazywanych w zdarzeniu
onerror. Po przerwaniu procesu odczytu przeglądarka tworzy zdarzenie błędu o nazwie ABORT_ERR i wywołuje
metodę onErrorHandler z tym błędem. Skrypt po prostu ignoruje ten błąd, gdyż przerwanie odczytu jest
obsługiwane gdzie indziej. Jednak inne błędy, które mogą się pojawić, będą obsłużone odpowiednim
komunikatem wyświetlanym użytkownikowi.
W tym przepisie pokazaliśmy zalety korzystania z interfejsu FileReader do przetwarzania pliku CSV
w przeglądarce. Wyniki parsowania pliku CSV mogą być filtrowane, wyświetlane i przygotowywane do
przekazania na serwer bez wykonywania działań na serwerze. Przepis zawiera funkcje odczytu pliku,
przerywania procesu i obsługi błędów, które mogą wystąpić. Może posłużyć jako wzór pozwalający rozpocząć
przetwarzanie plików na Twoich stronach internetowych.
Rozszerzone specyfikacje File API 349

Rozszerzone specyfikacje File API


Zastanawiasz się prawdopodobnie, czy możesz przechodzić do katalogu systemowego, tworzyć pliki i zapisywać
w nich dane. Do obsługi takich przypadków użycia są przygotowywane dwie nowsze specyfikacje oparte na
File API. Pierwszą jest File API: Directories and System, a drugą specyfikacja File API: Writer. Specyfikacje
te przeszły już przez wiele etapów prac, ale są ciągle zmieniane, więc nie zostały jeszcze zaimplementowane
w przeglądarkach. W rzeczywistości jedyną przeglądarką obsługującą w tym momencie te rozszerzone obiekty
i metody File API jest Chrome.

Uwaga
Począwszy od wersji 12, przeglądarka Chrome używa niestandardowej wersji wywołania
requestFileSystem — webkitRequestFileSystem. Parametry wywołania są takie same, lecz trzeba
używać odpowiednich wywołań zależnie od wersji Chrome. Można zakładać, że w miarę krzepnięcia
specyfikacji i wzrostu liczby przeglądarek obsługujących specyfikacje File API wywołania będą znowu
ogólne i takie same w różnych przeglądarkach.

Istnieje wiele obiektów i metod w obu omawianych API, lecz kilka jest kluczowych dla tego przepisu
i pracy z systemem plików oraz plikami. Oto kilka bardziej przydatnych interfejsów Directories and System:
„ FileSystem — reprezentuje system plików, z którym pracujemy,
„ Entry — reprezentuje ogólny wpis w systemie plików,
„ DirectoryEntry — reprezentuje katalog w systemie plików,
„ FileEntry — reprezentuje plik w systemie plików.

A oto niektóre z bardziej użytecznych interfejsów File API: Writer:


„ BlobBuilder — służy do zarządzania obiektami Blob danych używanych wraz z interfejsami plików,
„ FileSaver — służy do monitorowania zdarzeń zapisu i postępu,
„ FileWriter — służy do zapisywania i dopisywania do pliku oraz obcinania go.

Interfejsy DirectoryEntry i FileEntry są oparte na interfejsie Entry, który posiada standardowe metody
copy, move i remove. Oba omawiane API mają odpowiednie zdarzenia i zbiór możliwych błędów. Dodatkowo
API udostępniają zarówno wywołania asynchroniczne do użytku w sposób osadzony, jak i synchroniczne,
z których się korzysta w wątkach roboczych. Każdy z interfejsów posiada metody i atrybuty. Zalecamy Ci
przejrzenie najnowszych wersji specyfikacji w celu uzyskania aktualnych informacji na ich temat. W tabeli
14.4 opisujemy kilka spośród metod, które będziemy wykorzystywać w tym rozdziale.
Interfejs FileWriter ma również atrybuty length oraz position, które mogą być użyte do ustawienia
pozycji wskaźnika określającego miejsce dopisywania lub zapisywania informacji w pliku.
Jest wiele metod związanych z File API: Directories and System, w tym kopiujących i zmieniających
położenie plików. Są również atrybuty pliku i katalogu, które znajdziesz w zwykłym eksploratorze plików
i katalogów. Za pomocą tych metod i atrybutów możesz stosunkowo łatwo utworzyć swój własny eksplorator
plików i katalogów. Niestety nie mamy tutaj miejsca na utworzenie takiego przepisu, ale wszystkie niezbędne
informacje potrzebne do korzystania z tych metod i atrybutów możesz znaleźć w specyfikacji File API:
Directories and System. Należy zwrócić jednak uwagę na to, że ze względów bezpieczeństwa interfejsy te
mają do czynienia z systemem plików sztucznie „wygenerowanym” przez przeglądarkę dla domeny, z której
pochodzi strona. W następnym przepisie utworzymy plik w lokalnym systemie plików i zapiszemy w nim
jakieś dane.
350 Rozdział 14. Praca z plikami lokalnymi

Tabela 14.4. Kluczowe metody File API: Directories and System oraz Writer

Interfejs Metoda Przeznaczenie


FileSystem requestFileSystem Żąda udostępnienia systemu plików, w którym można zapisywać dane
aplikacji.
Entry copyTo Kopiuje Entry typu Directory albo File do podanej lokalizacji.
Entry getMetadata Pobiera metadane danego Entry.
Entry getParent Pobiera DirectoryEntry rodzica danego Entry.
Entry moveTo Przesuwa Entry z bieżącej lokalizacji w systemie plików do innej.
Entry Remove Usuwa obiekt Entry, niezależnie od tego, czy reprezentuje on plik,
czy katalog.
Entry toURL Zwraca adres URL danego obiektu Entry, który może być użyty jako
odwołanie.
FileEntry createWriter Tworzy instancję obiektu służącego do pisania do pliku.
FileEntry File Zwraca plik, na który wskazuje FileEntry.
FileWriter Seek Ustawia pozycję wskaźnika w pliku do wykonania następnego zapisu.
FileWriter Truncate Zmienia długość pliku przez skrócenie albo wydłużenie go.
FileWriter Write Zapisuje na bieżącej pozycji dane dostarczone w obiekcie File.

PRZEPIS DLA ZAAWANSOWANYCH


Tworzenie lokalnego pliku
W tym przepisie utworzymy stronę, która wykorzystuje lokalny plik tekstowy do przechowywania listy
adresów e-mail dodanych przez użytkownika. Strona pozwoli użytkownikowi dodać adres e-mail, który
zostanie dopisany do pliku, wyświetlić listę adresów e-mail z pliku oraz usunąć plik.

Uwaga
Jako że nie ma obecnie interfejsu związanego z uprawnieniami dotyczącymi bezpieczeństwa,
pozwalającego użytkownikowi zezwolić przeglądarce na dostęp do zapisywania pliku lokalnego,
musisz poinstruować przeglądarkę Chrome, że zezwolenie zostało udzielone, uruchamiając ją
z dwoma argumentami: --unlimited-quota-for-files oraz --allow-file-access-from-files.
Aby włączyć przeglądarkę Chrome z tymi argumentami, możesz uruchomić terminal w systemie
Mac OS i użyć następującego polecenia:
open /Applications/Google\ Chrome.app -n --args --unlimited-quota-for-files --allow-file-access-from-files
To polecenie uruchomi przeglądarkę Chrome, przekazując jej argumenty wiersza poleceń, które
nakazują przeglądarce umożliwienie dostępu do plików i ustawienie braku limitu rozmiaru plików.
Jeśli nie uruchomisz Chrome w ten sposób, otrzymasz błędy bezpieczeństwa lub uprawnień, które
uniemożliwią Ci dostęp do systemu plików1.

1
W czasie pracy nad tłumaczeniem książki udało mi się uruchomić przykład z przepisu w systemie Windows Vista bez potrzeby
stosowania opisywanych flag — przyp. tłum.
Tworzenie lokalnego pliku 351

Podany skrypt normalnie użyłby metody requestFileSystem, aby pobrać odwołanie do lokalnego systemu
plików. Ponieważ jednak specyfikacja ciągle się zmieniała, w przeglądarce Chrome 12 i nowszych jest
stosowana własna wersja tej metody — webkitRequestFileSystem. Ponadto skrypt korzysta z warunku
logicznego, tworząc obiekt Blob poprzez BlobBuilder albo WebKitBlobBuilder, w zależności od wersji
przeglądarki. Aby utworzyć stronę, wykonaj następujące kroki składające się na listing 14.5:
1. Utwórz pusty plik HTML i dodaj HTML-owe body z listingu 14.5, które zawiera element input
typu email, kilka elementów button oraz element div do wyświetlania listy adresów e-mail.
2. Dodaj funkcję fileErrorHandler, która będzie obsługiwać wszystkie błędy dotyczące katalogów
albo plików napotkane w kodzie asynchronicznym.
3. Dodaj w skrypcie funkcję fileAction, pokazaną na listingu 14.5, która pobiera odwołanie
do pliku i wywołuje odpowiednią funkcję w oparciu o działanie podjęte przez użytkownika.
4. Dodaj funkcje writeToFile, readFromFile oraz removeFile, które obsługują konkretne działania
z wykorzystaniem przekazanego odwołania do pliku.
5. Dodaj procedurę nasłuchu zdarzenia dotyczącą załadowania strony, która uruchomi funkcję
fileAction, pokazującą adresy e-mail, jeśli istnieje plik lokalny, w którym są zapisane.

Listing 14.5. Przechowywanie listy adresów e-mail w pliku lokalnym

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>14.5. Tworzenie pliku — zapis asynchroniczny</title>
<script>

// obsługa błędów File API


function fileErrorHandler(e) {
var msg = '';

// ustaw odpowiedni komunikat błędu na podstawie kodu


switch (e.code) {
case FileError.NOT_FOUND_ERR:
msg = 'Nie znaleziono pliku lub katalogu.';
break;
case FileError.SECURITY_ERR:
msg = 'Znaleziono problem dotyczący bezpieczeństwa.';
break;
case FileError.NOT_READABLE_ERR:
msg = 'Nie można odczytać pliku lub katalogu.';
break;
case FileError.ENCODING_ERR:
msg = 'Nieprawidłowa postać adresu pliku lub katalogu.';
break;
case FileError.NO_MODIFICATION_ALLOWED_ERR:
msg = 'Nie można zmodyfikować pliku lub katalogu.';
break;
case FileError.INVALID_STATE_ERR:
msg = 'Błąd stanu pliku lub katalogu.';
break;
case FileError.SYNTAX_ERR:
msg = 'Błąd składni przy zapisie do pliku.';
break;
352 Rozdział 14. Praca z plikami lokalnymi

case FileError.INVALID_MODIFICATION_ERR:
msg = 'Zażądano błędnej modyfikacji.';
break;
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'Brak miejsca z powodu przekroczenia ograniczenia rozmiaru zapisu.';
break;
case FileError.TYPE_MISMATCH_ERR:
msg = 'Nieprawidłowy typ pliku lub katalogu.';
break;
case FileError.PATH_EXISTS_ERR:
msg = 'Ścieżka już istnieje.';
break;
default:
msg = 'Nieznany błąd File API.';
break;
};

console.log('Błąd File API: ' + msg);


}

// funkcja służąca do pobrania obiektu fileSystem i uruchomienia odpowiedniego działania


function fileAction(actionType) {

var fileName = 'C:/HTML5FileText.txt';

// rozpocznij asynchroniczny proces działania z systemem plików


// pobierz obiekt fileSystem
window.webkitRequestFileSystem(window.PERSISTENT, 1024 * 1024, function(fileSystemObj) {

// pobierz odwołanie do pliku lub utwórz plik, jeśli go nie ma


fileSystemObj.root.getFile(fileName, {create:true}, function(fileEntry) {

// uruchom odpowiednią funkcję obsługi


switch (actionType) {
case 'write':
writeToFile(fileEntry);
break;
case 'read':
readFromFile(fileEntry);
break;
case 'remove':
removeFile(fileEntry);
break;
}
}, fileErrorHandler);
}, fileErrorHandler);
}

// asynchroniczna procedura obsługi dopisywania wpisu do pliku


function writeToFile(fileEntry) {

// utwórz obiekt służący do pisania do pliku


fileEntry.createWriter(function(fileWriter) {

// procedura obsługi zdarzenia onwriteend obiektu służącego do pisania do pliku


fileWriter.onwriteend = function(e) {
console.log('Zapis do pliku wykonany pomyślnie.');
readFromFile(fileEntry);
};
Tworzenie lokalnego pliku 353

// procedura obsługi zdarzenia onerror obiektu służącego do pisania do pliku


fileWriter.onerror = function(e) {
console.log('Zapis do pliku nie powiódł się: ' + e.toString());
};

// utwórz blob do wykorzystania przy dopisywaniu do pliku


var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)();
var emailToAdd = document.getElementById('emailAddress').value + "<br/>";
bb.append(emailToAdd);

// ustaw wskaźnik zapisu na koniec pliku, a następnie dodaj blob


fileWriter.seek(fileWriter.length);
fileWriter.write(bb.getBlob('text/plain'));

}, fileErrorHandler);
}

// asynchroniczna procedura obsługi czytania pliku


function readFromFile(fileEntry) {

// pobierz obiekt pliku


fileEntry.file(function(file) {

// utwórz obiekt służący do odczytu pliku


var reader = new FileReader();

// procedura obsługi zdarzenia onloadend obiektu służącego do odczytu pliku


reader.onloadend = function(e) {
// wyświetl wyniki z pliku
var emailDiv = document.getElementById('emailList');
emailDiv.innerHTML = this.result;
};

// odczytaj plik
reader.readAsText(file);
}, fileErrorHandler);
}

// asynchroniczna procedura obsługi usuwania pliku


function removeFile(fileEntry) {

// usuń plik
fileEntry.remove(function() {
console.log('Plik został usunięty.');
}, fileErrorHandler);
}

// domyślny odczyt pliku


window.addEventListener('load',fileAction('read'),false);

</script>
</head>
<body>
<input type="email" id="emailAddress" /><button onClick="fileAction('write');">Dodaj adres e-mail</button>
<button onClick="fileAction('read');">Odczytaj adresy e-mail</button>
<button onClick="fileAction('remove');">Usuń plik</button>
<br/><br/>
<div id="emailList"></div>
</body>
</html>
354 Rozdział 14. Praca z plikami lokalnymi

Aby uruchomić stronę utworzoną na listingu 14.5, włącz Chrome za pomocą polecenia zawierającego
argumenty dotyczące uprawnień do lokalnego systemu plików. Jeśli przeglądarka Chrome nie zostanie
uruchomiona z tymi parametrami, strona nie będzie mogła wykonywać działań na plikach lokalnych.
Po załadowaniu, w przypadku gdy plik już istnieje, strona ma załadować adresy e-mail z pliku i je wyświetlić.
W tym celu strona wywołuje funkcję fileAction z wartością read. W funkcji fileAction wykonywany jest
szereg asynchronicznych wywołań funkcji. Najpierw pobierane jest odwołanie do systemu plików za pomocą
metody webkitRequestFileSystem, której przekazywana jest flaga dotycząca trwałości plików, informacja
dotycząca spodziewanej wielkości miejsca potrzebnego na przechowywanie danych, funkcja wywołania
zwrotnego oraz procedura obsługi błędów. Flaga trwałości informuje przeglądarkę, co zrobić z plikami,
a konkretnie: czy pozwolić przeglądarce usunąć pliki w razie potrzeby (TEMPORARY), czy je pozostawić
(PERSISTENT). W tym przykładzie utworzyliśmy jedną, ogólną procedurę obsługi błędów dla wszystkich
wywołań metod dotyczących katalogów i plików, mimo że niektóre kody błędów mogą nie mieć zastosowania.
Jednak zbiór kodów błędów jest taki sam dla całego File API. Możesz oczywiście mieć oddzielne procedury
obsługi błędów z bardziej wyspecjalizowanymi komunikatami błędów i działaniami dostosowanymi do
własnych potrzeb.
Po uzyskaniu uchwytu systemu plików skrypt wykonuje metodę getFile na głównym obiekcie systemu
plików, przekazując jej nazwę pliku, parametr dotyczący otwarcia pliku, funkcję wywołania zwrotnego
dotyczącego pomyślnego wykonania metody i procedurę obsługi błędów. W tym przypadku informujemy
metodę, żeby tworzyła plik, jeśli go nie ma, poprzez ustawienie flagi create na true. Jeśli ta flaga byłaby
ustawiona na false, metoda nie utworzyłaby nowego pliku i działałaby jedynie jako funkcja otwierająca plik.
Po uzyskaniu obiektu pliku skrypt wywołuje następnie funkcję readFromFile, która kontynuuje asynchroniczny
przebieg działań, pobierając plik i odczytując go poprzez instancję służącego do tego obiektu. Kiedy obiekt
zakończy czytanie pliku, skrypt wyświetli wyniki w odpowiednim elemencie div, jak pokazano na rysunku 14.4.

Rysunek 14.4. Przykładowy wynik pokazujący adresy e-mail odczytane z pliku


po ich uprzednim dodaniu

Teraz, kiedy plik już istnieje, użytkownik może dodawać adresy e-mail, wprowadzając je i klikając przycisk
Dodaj adres e-mail, który uruchomi fileAction. Funkcja fileAction uruchomi writeToFile, w której
utworzysz obiekt fileWriter służący do obsługi zapisu tekstu do pliku. Skrypt najpierw ustawia procedury
obsługi dotyczące momentu zakończenia działania przez fileWriter (onwriteend) oraz przypadku, kiedy
zapis tekstu z jakiegoś powodu się nie powiódł (onerror). Potem za pomocą BlobBuilder tworzy obiekt Blob
Podsumowanie 355

zawierający wprowadzony adres e-mail. Odpowiedni interfejs jest w tym przypadku wybierany za pomocą
wyrażenia warunkowego, gdyż jest on różny w różnych wersjach przeglądarki Chrome. Następnie skrypt
za pomocą metody seek obiektu fileWriter przesuwa wskaźnik położenia na koniec pliku w oparciu
o właściwość length. Pamiętaj, że kiedy adres jest dodawany do pliku, na końcu każdego adresu e-mail
należy dopisać HTML-owy znacznik przejścia do nowego wiersza (<br/>) dla ułatwienia wyświetlania
wielu adresów e-mail w elemencie div po wczytaniu pliku. Możesz sformatować dane z pliku w układzie,
który najlepiej pasuje do pokazywanych informacji. Po zakończeniu zapisywania do pliku nowych adresów
e-mail skrypt uruchamia odczyt, aby zaktualizować listę wyświetlaną na stronie.
Dodatkowo skrypt udostępnia możliwość usuwania pliku przez wywołanie funkcji remove na obiekcie
FileEntry. Spowoduje to usunięcie pliku z systemu plików. Jeśli opuścisz stronę i powrócisz bez usuwania
pliku, plik nie zniknie, gdyż w wywołaniu webkitRequestFileSystem poprosiłeś system plików, żeby plik był
trwały. W najbliższych miesiącach powinien nastąpić postęp w obsłudze specyfikacji File API: Directories
and System oraz File API: Writer przez przeglądarki różnych producentów.

Podsumowanie
W tym rozdziale dowiedziałeś się, jak wyświetlać atrybuty plików i odczytywać pliki w przeglądarce za pomocą
nowego File API. Dodatkowo poznałeś kilka nowszych specyfikacji dotyczących plików, które dodają funkcje
inne niż tylko odczytywanie plików. Przenosząc działania, które normalnie musiałyby być wykonywane za
pomocą przetwarzania po stronie serwera, do przeglądarki, możesz rozłożyć przetwarzanie na komputery
klienta i poprawić działanie stron z punktu widzenia odwiedzającego.
356 Rozdział 14. Praca z plikami lokalnymi
15
Integracja z urządzeniami
przenośnymi

W ciągu ostatnich 20 lat HTML przeszedł długą drogę i cały czas ewoluuje dzięki pracy twórców
przeglądarek i różnych grup, takich jak grupy robocze W3C. HTML5, jaki znamy, jest zbiorem
HTML-owych znaczników i atrybutów, javascriptowych API oraz stylów CSS. W tej książce omówiliśmy
wiele spośród rozszerzających go API, które zostały dodane w ramach HTML5. Jednak prace nad ulepszaniem
działania przeglądarek z punktu widzenia użytkownika, w szczególności w związku z coraz powszechniejszym
korzystaniem z urządzeń przenośnych, nie zostały zaniechane, a nawet są intensywniejsze niż kiedykolwiek.
W ciągu ostatnich kilku lat nastąpiła prawdziwa eksplozja w branży urządzeń przenośnych, które teraz
znacząco różnią się od telefonów komórkowych z przeszłości. Nie są to już telefony komórkowe zawierające
wiele innych funkcji, lecz urządzenia przenośne, w których telefon jest tylko jedną z funkcji.
W tym rozdziale zapoznasz się z jednym z najbardziej ekscytujących i nowych obszarów dotyczących
działania przeglądarek: integracją z różnymi funkcjami urządzeń (mowa tu o aparatach fotograficznych,
mikrofonach, żyroskopach, akcelerometrach) i aplikacjami, takimi jak bazy danych kontaktowych, aplikacje
kalendarza i galerie zdjęć. Te API mają umożliwić stronom internetowym oferowanie szerokiego zakresu
funkcji, w tym wideokonferencji, planowania spotkań w kalendarzu użytkownika, dodawania zdjęć z galerii
użytkownika, sprawdzania poziomu naładowania akumulatorów lub innych zadań, które zwykle były
zarezerwowane dla natywnych aplikacji urządzeń albo wyspecjalizowanych wtyczek. Niedługo również twórcy
stron internetowych będą mogli korzystać z tych funkcji.

Krótka historia API urządzeń


W maju 2009 roku w organizacji W3C została założona grupa robocza Device APIs and Policy, której celem
było utworzenie zbioru API i zdarzeń po stronie klienta, służących do interakcji ze sprzętem i aplikacjami.
Pierwotnie w jej statucie zostało zdefiniowanych wiele różnych API, których zakres działania był dosyć
rozległy i nakładał się na prace kilku innych grup. Przewidywany termin zakończenia prac został w statucie
grupy ustalony na lipiec 2011 roku. Jednak z różnych powodów grupa zmieniła statut w listopadzie 2010
roku na taki, który zawierał zawężony zbiór API. Grupa nosi teraz nazwę Device APIs Working Group i jej
celem jest stworzenie kilku API skoncentrowanych na integracji z urządzeniami, a to wszystko pomiędzy
listopadem 2010 a czerwcem 2013 roku, który jest nowym terminem ukończenia prac.
358 Rozdział 15. Integracja z urządzeniami przenośnymi

Jak możesz sobie wyobrazić, zakres tematyczny pokrywany przez te API jest dość szeroki. Grupa robocza
robi duże postępy w tworzeniu API i specyfikacji zdarzeń, które mogą zostać wykorzystane przez producentów
przeglądarek. Mimo że jest jeszcze wcześnie, niektórzy producenci już zaczęli je włączać do swoich produktów.
Zbiór API i zdarzeń urządzeń grupy roboczej Device APIs zawiera następujące pozycje:
„ Application Registration API — umożliwia stronom internetowym rejestrowanie się w systemie
jako aplikacje i pozwala im na obsługę wywołań z identyfikatorem rejestracji.
„ Battery Status API* — udostępnia informacje dotyczące akumulatorów urządzenia.
„ Beep API — umożliwia sterowanie sygnałami dźwiękowymi systemu urządzenia.
„ Calendar API* — umożliwia odczyt wpisów w kalendarzu na urządzeniu.
„ Contacts API* — umożliwia odczyt kontaktów oraz zawartych w nich informacji pochodzących
z aplikacji przechowującej kontakty w urządzeniu.
„ Gallery API* — umożliwia dostęp do galerii urządzenia, która może zawierać pliki dźwiękowe,
wideo i zdjęcia1.
„ Generic Sensor API — umożliwia integrację i obsługę różnych czujników w urządzeniu.
„ HTML Media Capture* — udostępnia atrybuty i funkcje HTML-owe do przechwytywania
w urządzeniu mediów, takich jak dźwięk, wideo i obraz.
„ Media Capture API* — udostępnia interfejs programistyczny do przechwytywania mediów
za pomocą kamery i mikrofonu urządzenia.
„ Menu API — udostępnia możliwość kontrolowania menu aplikacji urządzenia.
„ Messaging API* — pozwala stronie klienta wysłać wiadomość opartą o schematy URI mms,
sms albo mailto.
„ Network Information API* — pobiera aktualny typ połączenia sieciowego urządzenia.
„ Tasks API — umożliwia dostęp do zadań użytkownika zarządzanych na urządzeniu.
„ Vibration API — udostępnia kontrolę nad wibracjami urządzenia2.

Wymienione API są bardzo młode i jedynie wyjątkowo są implementowane w przeglądarkach.


W rzeczywistości w chwili pisania tej książki tylko Contacts API doszło do stanu zgodnego z ostatnią wersją
roboczą specyfikacji3. Specyfikacje oznaczone gwiazdką (*) są opublikowane w jakiejś formie roboczej
na witrynie www.w3.org. W tym rozdziale omówimy część z powyższych API, prezentując te bardziej
rozwinięte, oraz zamieścimy przepisy oparte na ich implementacjach, które są aktualnie dostępne. Biorąc
pod uwagę krótki czas istnienia przedstawionych API, jest zbyt wcześnie, aby stwierdzić, które przeglądarki
będą obsługiwać poszczególne funkcje.

1
Prace nad tą specyfikacją nie są kontynuowane, a sama specyfikacja powinna być uważana za historyczną — przyp. tłum.
2
Na stronie www.w3.org znajduje się już opublikowana wersja robocza tej specyfikacji — przyp. tłum.
3
12 lipca 2012 roku została opublikowana wersja robocza specyfikacji Pick Contacts Intent, która docelowo ma zastąpić
Contacts API — przyp. red.
Contacts API 359

Contacts API
Contacts API umożliwia użytkownikowi komunikującemu się ze stroną w przeglądarce udostępnianie tej
stronie informacji o kontakcie (lub kontaktach) z lokalnej aplikacji użytkownika przechowującej kontakty.
Użytkownik może się zdecydować na udostępnianie stronie jedynie określonych pól. Dodatkowo użytkownik
może się zdecydować udostępnić tylko kontakty spełniające kryteria wymagane przez stronę, na przykład
kontakty, które mają w polu przechowującym miasto wartość „Warszawa”. Jest to API tylko do odczytu,
w związku z czym obecnie nie mogą być dodawane nowe wpisy. Specyfikacja Contacts API zaleca stosowanie
aktualnie używanych formatów, takich jak vCard, do dodawania albo aktualizacji kontaktów w repozytorium
kontaktów użytkownika. Ponieważ Contacts API umożliwia dostęp do informacji osobistych i poufnych,
przeglądarka musi poprosić o zgodę użytkownika na udzielenie dostępu do tego API.
Po udzieleniu zgody JavaScript na stronie może użyć interfejsu Contacts, aby rozpocząć odnajdywanie
kontaktów. Interfejs Contacts ma jedną metodę, find, która pozwala mu odnaleźć jeden kontakt lub większą
ich liczbę, jak pokazano w poniższej sygnaturze metody find:
void find (DOMString[] pola, ContactFindCB funkcjaSukcesu, opcjonalnie ContactErrorCB funkcjaBłędu,
´opcjonalnie ContactFindOptions opcje);

Poniżej wyjaśniamy parametry funkcji find:


„ DOMString[] pola — tablica elementów typu DOMString, reprezentująca pola zwracanego rekordu
kontaktu;
„ funkcjaSukcesu — funkcja wywołania zwrotnego, uruchamiana w przypadku pomyślnego wykonania
funkcji find;
„ funkcjaBłędu — opcjonalna funkcja wywołania zwrotnego, uruchamiana w przypadku błędu;
„ opcje — opcje wyszukiwania, służące do filtrowania rekordów kontaktu, na przykład na podstawie
imienia.

Aby uniknąć zablokowania strony podczas wyszukiwania kontaktów, metoda find wykorzystuje
funkcje zwrotne, które są uruchamiane w przypadku jej pomyślnego lub niepomyślnego wykonania. Jeśli
funkcja odnalazła pasujące kontakty oraz pola, które powinny zostać zwrócone, przeglądarka wywołuje
funkcję wywołania zwrotnego dotyczącą pomyślnego wykonania z przekazaną jej w zmiennej tablicą
wyników. Jeśli metoda find zakończyła działanie bez błędu, ale nie zwróciła żadnych wyników, do funkcji
wywołania zwrotnego dotyczącego pomyślnego wykonania zostanie przekazana wartość null, oznaczająca
nieodnalezienie dopasowanych rekordów kontaktów.
Zwracany rekord kontaktu jest instancją interfejsu Contacts, który zawiera szereg atrybutów. Typami
niektórych z tych atrybutów są inne interfejsy dotyczące kontaktów, takie jak ContactName, ContactAddress
i tak dalej, gdyż kontakt może w rzeczywistości mieć wiele elementów składowych, na przykład kilka
adresów. W tabeli 15.1 pokazujemy atrybuty interfejsu Contacts. Nie będziemy w tym miejscu opisywać
wszystkich tych interfejsów, ale łatwo można je odnaleźć w aktualnej wersji roboczej specyfikacji na stronie
http://www.w3.org/TR/contacts-api/.

Uwaga
W chwili pisania tej książki nie było przeglądarki obsługującej Contacts API, ale prace nad specyfikacją
trwały. Poniższy przepis udostępniamy jako przykład — może on zawierać błędy albo różnice w stosunku
do rzeczywistych implementacji w ich ostatecznej postaci.
360 Rozdział 15. Integracja z urządzeniami przenośnymi

Tabela 15.1. Atrybuty interfejsu Contacts

Typ atrybutu Atrybut Opis


DOMString id unikalny identyfikator kontaktu
DOMString displayName nazwa kontaktu
ContactName name pełna nazwa kontaktu
DOMString nickname pseudonim kontaktu
ContactField phoneNumbers jeden lub więcej numerów telefonów kontaktu w polu
ContactField
ContactField emails jeden lub więcej adresów e-mail kontaktu w polu ContactField
ContactAddress addresses jeden lub więcej adresów kontaktu w obiekcie ContactAddress
ContactField ims jeden lub więcej identyfikatorów komunikatora kontaktu w polu
ContactField
ContactOrganization organizations jedna lub więcej organizacji powiązanych z tym kontaktem
w obiekcie ContactOrganization
Date birthday data urodzenia osoby, której dotyczy kontakt, umieszczona
w obiekcie Date
DOMString note pole notatek w rekordzie kontaktu
ContactField photos jeden lub więcej adresów URL zdjęć dotyczących kontaktu
[DOMString] categories tablica wartości typu DOMStrings, reprezentująca kategorie
przypisane do tego kontaktu
ContactField urls jeden lub więcej adresów URL kontaktu w polu ContactField

PRZEPIS DLA POCZĄTKUJĄCYCH


Pobieranie wszystkich kontaktów i numerów telefonu
Mimo że raczej nie jest zalecane pobieranie wszystkich kontaktów użytkownika, czasami może to być
przydatne. Ten przepis wykorzysta metodę find interfejsu Contacts wraz z funkcjami wywołania zwrotnego
dotyczącymi pomyślnego wykonania oraz błędu, aby pobrać wszystkie kontakty, sprawdzić, czy dany kontakt
posiada numer telefonu komórkowego, i wyświetlić nazwę kontaktu oraz numer telefonu komórkowego
w elemencie div na stronie. Aby utworzyć kod przepisu, wykonaj następujące kroki, prowadzące do listingu 15.1:
1. Utwórz pustą stronę HTML zawierającą element button oraz element div przeznaczony
do pokazywania wyników. Dodaj do przycisku procedurę obsługi kliknięcia, uruchamiającą
metodę findAllContacts.
2. Dodaj do strony znaczniki script oraz metodę findAllContacts, która uruchamia żądanie
pobrania kontaktów.
3. Dodaj funkcje zwrotnych wywołań contactsFindSuccess oraz contactsError, służących do
obsługi zdarzeń dotyczących — odpowiednio — pomyślnego i niepomyślnego wykonania
żądania pobrania kontaktów.
Pobieranie wszystkich kontaktów i numerów telefonu 361

Listing 15.1. Pobieranie i wyświetlanie wszystkich numerów telefonów komórkowych z kontaktów

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>15.1. Pobierz wszystkie kontakty</title>
<script>
// funkcja inicjalizująca stronę
function init() {
// ustaw procedurę obsługi przycisku
var btnFindAll = document.getElementById('findAll');
btnFindAll.addEventListener('click',findAllContacts,false);
}

// funkcja wywołania zwrotnego, dotycząca pomyślnego wykonania żądania odnalezienia kontaktów


function contactsFindSuccess(contacts) {

// pobierz referencję do elementu div służącego do wyświetlania wyników


var divResults = document.getElementById('divResults');

// przejdź w pętli przez pobrane kontakty


for (var i in contacts) {

// dla każdego kontaktu przejdź w pętli przez wszystkie numery telefonów


for (var j in contacts[i].phoneNumbers) {

// sprawdź, czy numer telefonu jest numerem telefonu komórkowego


if (contacts[i].phoneNumbers[j].type === 'mobile') {

// wyświetl nazwę kontaktu i numer telefonu


divResults.innerHTML += contacts[i].displayName + ' (' + contacts[i].phoneNumbers[j].value + ')';
}
}
}
}

// funkcja wywołania zwrotnego, dotycząca niepomyślnego wykonania żądania odnalezienia kontaktów


function contactsError(error) {
// obsłuż odpowiednio błąd, w tym przypadku po prostu go wyświetl
alert(error.code);
}

// funkcja służąca do odnajdywania kontaktów


function findAllContacts() {

// sprawdź, czy Contacts API jest dostępne


if (navigator.contacts) {

// ustaw tablicę pól do pobrania


var arrFields = ['displayName', 'phoneNumbers'];

// wykonaj metodę find na kontaktach


navigator.contacts.find(arrFields, contactsFindSuccess, contactsError);

} else {

// powiadom użytkownika, że Contacts API nie jest obsługiwane


362 Rozdział 15. Integracja z urządzeniami przenośnymi

alert('Contacts API nie jest obsługiwane w tej przeglądarce... jeszcze');


}
}

// inicjalizuj stronę po jej załadowaniu


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Pobierz wszystkie kontakty z numerami telefonów komórkowych</h1>
<button id="findAll">Znajdź wszystkie kontakty</button>
<div id="divResults"></div>
</body>
</html>

W powyższym przykładzie, kiedy użytkownik kliknie przycisk Znajdź wszystkie kontakty, przeglądarka
wywoła funkcję findAllContacts, która wykonuje zapytanie za pomocą interfejsu Contacts. W funkcji
findAllContacts kod najpierw sprawdza, czy interfejs Contacts jest dostępny. Jeśli nie jest, przeglądarka
pokazuje powiadomienie, ale możesz zastąpić je alternatywnym zachowaniem, które lepiej sprawdzi się
w Twoim przypadku. Jeśli interfejs Contacts jest dostępny, kod tworzy tablicę pól, które mają być zwrócone
przez metodę find tego interfejsu. Następnie kod wywołuje metodę find i przekazuje jej tablicę pól oraz
funkcje wywołania zwrotnego dotyczące pomyślnego i niepomyślnego wywołania metody.
Pole phoneNumbers, pobrane za pośrednictwem interfejsu Contacts, ma format interfejsu ContactField.
Ten interfejs składa się z atrybutów type, value oraz pref, jak pokazano w tabeli 15.2.

Tabela 15.2. Atrybuty interfejsu ContactField

Typ atrybutu Atrybut Opis


DOMString type Kategoryzacja wartości
DOMString value Właściwa wartość pola
Boolean pref Flaga wskazująca na to, czy to pole jest główną, czy preferowaną wartością

Atrybut type ma typ DOMString i w przypadku pola phoneNumbers atrybut ten może mieć wartość home
albo mobile. Atrybut value to sam numer, a atrybut pref jest wartością logiczną, która mówi, czy mamy
do czynienia z wartością preferowaną, czy główną dla tego kontaktu. Przy wywołaniu funkcji zwrotnej
dotyczącej pomyślnego wykonania, czyli contactsFindSuccess, przeglądarka przekazuje jej zbiór wyników.
Jest on tablicą instancji interfejsu dotyczącego kontaktów, przez które można przejść w pętli. W tym przypadku
nazwaliśmy zmienną z wynikami contacts.
W funkcji wywołania zwrotnego dotyczącej pomyślnego wykonania przepis najpierw tworzy odwołanie
do elementu div, który jest przeznaczony na wyniki i znajduje się na stronie, tak abyś mógł wyświetlać
wszystkie pasujące wyniki. Następnie funkcja przechodzi w pętli przez tablicę kontaktów — dla każdego
kontaktu przepis przechodzi przez jego numery telefonów (phoneNumbers), a dla każdego numeru telefonu
kod sprawdza type, żeby dowiedzieć się, czy to numer telefonu komórkowego. Jeśli tak jest, kod wyświetla
nazwę oraz numer telefonu w elemencie div.
Użyta w tym przepisie metoda find interfejsu Contacts wykonuje zapytanie o wszystkie kontakty,
przekazując funkcje wywołania zwrotnego dotyczące pomyślnego i niepomyślnego wykonania. Metoda
Messaging API 363

ta może przyjąć czwarty, opcjonalny parametr typu ContactFindOptions. Według specyfikacji API Contacts
sposób implementacji tego interfejsu zależy od konkretnej przeglądarki, jednak celem tego interfejsu jest
stworzenie filtra nakładanego na podane pola kontaktów. Jest to prosta metoda filtrująca i dopasuje do
zadanego ciągu dowolne z podanych pól. Na przykład przy podaniu wartości filtra „son” i pobieraniu pól
displayName oraz emails dowolny kontakt, który posiada ciąg „son” w którymś z tych zbiorów pól, na przykład
„sonny@....com” albo „Chuck Hudson”, zostanie dopasowany. W tym przypadku przekazany metodzie
czwarty parametr wyglądałby następująco:
{filter: 'son'}

Do momentu, kiedy jakaś przeglądarka rzeczywiście zaimplementuje Contacts API z filtrowaniem,


możemy się tylko domyślać, w jakim stopniu będziemy mogli je kontrolować, ale wydaje się, że filtr
powinien umożliwiać jakiś prosty sposób selekcji kontaktów z urządzenia.

Uwaga
Contacts API jest zaprojektowane wyłącznie jako usługa tylko do odczytu danych o kontaktach
zapisanych w urządzeniu. Aby wykonać dodawanie i aktualizację kontaktów w pamięci danych
urządzenia, powinieneś umieścić dane kontaktu w standardowym formacie aplikacji z kontaktami,
na przykład tekstowym vCard, a następnie odwołać się do tekstu tej karty poprzez element href,
tak aby użytkownik mógł pobrać kartę do swojej aplikacji.

Messaging API
Messaging API zaprojektowano, by umożliwić stronie wykorzystanie schematów URI sms, mms oraz mailto
do wysyłania komunikatów na określony adres. API jest stosunkowo proste, ponieważ ma jeden interfejs
i jedną metodę: device i sendMessage. Interfejs device ma być częścią interfejsu nawigatora, więc aby sprawdzić
obsługiwanie Messaging API, możesz użyć następującego kodu:
if (navigator.device.sendMessage) {
… tutaj umieść Twój kod sendMessage …
}

Jeśli przeglądarka obsługuje Messaging API, możesz użyć metody sendMessage, aby wysłać wiadomość
do określonego odbiorcy. Poniżej pokazano składnię tej metody:
void sendMessage (DOMString to [,Blob attachments] [, messagingErrorCB]);

Parametr to przyjmuje URI (mające schemat sms, mms albo mailto) i akceptuje łańcuchy zapytania
zawierające elementy body i podobne. Pole attachments może być wykorzystane na zdjęcia albo wideo,
a ostatni parametr jest opcjonalną procedurą obsługi błędów. Aby wysłać prostą wiadomość poprzez SMS,
możesz wywołać sendMessage w następujący sposób:
navigator.device.sendMessage('sms:+17705551212?body=Wkrótce%20przyjadę');

Jeśli podana jest funkcja wywołania zwrotnego obsługi błędów — co jest zalecane, ponieważ inaczej nie
będziesz wiedzieć, że wywołanie sendMessage się nie powiodło — to przeglądarka przekaże jej obiekt błędu
z jego kodem. Tabela 15.3 zawiera listę możliwych kodów błędów.
364 Rozdział 15. Integracja z urządzeniami przenośnymi

Tabela 15.3. Wartości kodów błędów Messaging API

Wartość Nazwa stałej Opis


0 UNKNOWN_ERROR Wystąpił nieznany błąd.
1 INVALID_ARGUMENT_ERROR Metodzie sendMessage został przekazany nieprawidłowy parametr.
3 TIMEOUT_ERROR Żądanie wysłania wiadomości przekroczyło dozwolony czas oczekiwania.
4 PENDING_OPERATION_ERROR Przeglądarka oczekuje już wywołania zwrotnego.
5 IO_ERROR Wystąpił błąd komunikacji.
6 NOT_SUPPORTED_ERROR Metoda sendMessage nie jest obsługiwana.
20 PERMISSION_DENIED_ERROR Użytkownik lub przeglądarka nie zezwolili na wywołanie metody.
30 MESSAGE_SIZE_EXCEEDED Ograniczenie rozmiaru dla tego typu wiadomości zostało przekroczone.

Mimo że Messaging API jest krótką specyfikacją, jej konsekwencje są niezwykłe. Strony będą mogły wysyłać
wiadomości tekstowe, wideo i zdjęcia poprzez wiadomości MMS i e-mail prosto z przeglądarki urządzenia.

Wskazówka
Oczekuje się, że metoda sendMessage zaimplementowana w przeglądarkach będzie obsługiwać
funkcjonalności URI. Wtedy możliwe będzie wysłanie wiadomość SMS do wielu odbiorców, oddzielając
po prostu numery przecinkami, na przykład „sms: +48501123456,+48601123456…”.

Network Information API


Aby możliwe było ograniczenie funkcjonalności oraz przesyłanych danych, co zapewni lepsze działanie
aplikacji, konieczne jest określenie szybkości połączenia przeglądarki z siecią. Network Information API
ma na celu udostępnienie prostego atrybutu type, który będzie oznaczać rodzaj połączenia aktualnie
wykorzystywanego przez urządzenie. Na podstawie tej wartości strona mogłaby zmienić swoje zachowanie,
aby dostosować się do dostępnej prędkości przesyłu danych. Rodzaj połączenia może przyjąć jedną
z następujących wartości: unknown, ethernet, wifi, 2g, 3g, 4g oraz none. W HTML5 dostępny jest nowy
interfejs o nazwie Connection, który posiada jeden atrybut: type. Aby odczytać aktualny rodzaj połączenia,
możesz wykorzystać następujący kod:
var connectionType = navigator.connection.type;

Znając rodzaj połączenia, możesz w sposób dynamiczny zmieniać arkusze stylów pomiędzy wersjami
pełną i lekką albo modyfikować działanie strony, pobierając jedynie częściowe wyniki w przypadku
wolniejszego połączenia.
Oprócz interfejsu Connection mamy jeszcze dwa nowe zdarzenia: online i offline. Są one powiązane
z obiektem window i mogą być używane do sprawdzania zmian rodzaju połączenia (w przypadku zdarzenia
online) albo obsługi sytuacji rozłączania połączenia z siecią.

Wskazówka
Zdarzenie online może być wysyłane wiele razy, w przypadku kiedy użytkownik zmienia rodzaje
połączenia. Z tego powodu w Twoim kodzie funkcjonalność wykonywana w przypadku tego zdarzenia
powinna być zminimalizowana.
Przechwytywanie obrazu za pomocą elementu input typu file 365

HTML Media Capture


HTML Media Capture ma instruować przeglądarkę, by uruchomiła narzędzia przechwytywania mediów
na podstawie nowych parametrów i atrybutów elementu input typu file. Na podstawie informacji o tym,
jakie rodzaje informacji przyjmować w elemencie input typu file oraz czego użyć do „przechwycenia”
danych wejściowych, przeglądarka uruchamia w urządzeniu odpowiedni mechanizm przechwytywania.
Przechwytywać można obraz, wideo albo dźwięk, ponieważ wszystkie one są rodzajem mediów, które są
zazwyczaj dostępne w urządzeniach posiadających kamery i mikrofony, takich jak smartfony, tablety i laptopy.
Zazwyczaj element input typu file otwiera okno przeglądania plików, pozwalające wybrać odpowiedni
plik. HTML Media Capture używa atrybutu accept elementu input typu file do zalecania sposobu wyboru
odpowiedniego pliku, na przykład aparatu fotograficznego w przypadku obrazu. Oto trzy różne dostępne
wartości atrybutu accept:
„ image/* — oznacza przyjmowanie zdjęć z aparatu albo galerii zdjęć użytkownika,
„ audio/* — oznacza przyjmowanie danych dźwiękowych poprzez nagrywanie za pomocą
mikrofonu urządzenia,
„ video/* — oznacza przyjmowanie plików wideo nagranych przez kamerę i mikrofon urządzenia.

Można dodać dodatkowy atrybut, sugerujący, w jaki sposób przeglądarka powinna przechwycić dane
wejściowe. Atrybut capture może przyjąć jedną z czterech wartości: camera, camcorder, microphone albo
filesystem. Wartością domyślną, jeśli nie podano atrybutu capture, jest filesystem. Przykładowy element
input typu file służący do przechwytywania dźwięku z mikrofonu wyglądałby następująco:
<input type="file" accept="audio/*" capture="microphone">

Za pomocą atrybutów accept i capture będziesz mógł w ciekawy sposób wykorzystywać mikrofon
i kamerę urządzenia. Istnieją już grupy pracujące nad implementacją wywołań dwukierunkowej transmisji
strumieniowej audio/wideo pomiędzy urządzeniami wykorzystującymi HTML Media Capture i inne
technologie HTML5, na przykład WebSocket.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Przechwytywanie obrazu za pomocą
elementu input typu file
W tym przepisie wykorzystamy atrybuty accept i capture elementu input typu file do tego, aby przeglądarka
uruchomiła ekran służący do wykonywania zdjęć z wykorzystaniem wbudowanego aparatu fotograficznego
urządzenia. Za pomocą tego ekranu użytkownik zrobi zdjęcie, które następnie zostanie zwrócone na stronę.
HTML Media Capture załaduje plik zwróconego na stronę zdjęcia do nowego elementu image, przeskalowanego
do miniatury i dodanego do strony. Aby utworzyć kod z listingu 15.2, wykonaj następujące kroki:
1. Utwórz pustą stronę HTML z elementem input typu file oraz elementem div przeznaczonym
na miniatury.
2. Dodaj do strony znaczniki script oraz funkcję handleCapture, która obsługuje dostarczany plik
za pomocą metody onChange elementu input.
366 Rozdział 15. Integracja z urządzeniami przenośnymi

Uwaga
W momencie pisania tego tekstu ten przepis działał na urządzeniach z systemem Android 3.0 bądź
nowszym, które obsługują HTML Media Capture, dlatego zrzuty ekranu do tego przepisu pochodzą
z tabletu z systemem Android.

Listing 15.2. Przechwytywanie obrazu z aparatu

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>15.2. Przechwytywanie mediów</title>
<script>
// funkcja obsługująca HTML-owe przechwytywanie mediów z pliku
function handleCapture(files) {

// ustaw wzorzec dopasowujący obrazek


var imageType = /image.*/;

// spodziewamy się tylko jednego zdjęcia w danym momencie, ale przejdź w pętli przez wszystkie dostarczone pliki
for (var i = 0; i < files.length; i++) {

// pobierz plik z tych, które zostały przekazane w parametrze


var file = files[i];

// sprawdź, czy plik jest obrazkiem


if (file.type.match(imageType)) {

// utwórz nowy element image


var newImg = document.createElement("img");
newImg.classList.add("obj");
newImg.file = file;
newImg.style.maxHeight = "100px";
newImg.style.maxWidth = "100px";

// dodaj nowy element image do obszaru miniatur


var thumbnails = document.getElementById('thumbnails');
thumbnails.appendChild(newImg);

// załaduj zawartość pliku do elementu image


var reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) { aImg.src = e.target.result; };
})(newImg);
reader.readAsDataURL(file);
}
}
}
</script>
</head>
<body>
<h2>Przechwytywanie mediów — aparat fotograficzny</h2>
<input type="file" accept="image/* "
capture="camera"
onChange="handleCapture(this.files)"></input>
<div id="thumbnails"></div>
</body>
</html>
Przechwytywanie obrazu za pomocą elementu input typu file 367

Po załadowaniu w przeglądarce strona pokazuje zwykły przycisk Wybierz plik, jak pokazano na rysunku 15.1.

Rysunek 15.1. Przykładowy wynik działania listingu 15.2, pokazujący stronę


gotową do przechwycenia zdjęcia

Kiedy użytkownik kliknie przycisk, przeglądarka po odczytaniu wartości atrybutów accept i capture wie,
że ma wykorzystać aparat fotograficzny, co w tym przypadku powoduje uruchomienie ekranu robienia zdjęć
systemu Android, jak pokazano na rysunku 15.2.

Rysunek 15 2. Domyślny ekran aparatu fotograficznego wyświetlony na urządzeniu


w celu przechwycenia obrazu

Po zrobieniu zdjęcia i przekazaniu na stronę jego pliku przeglądarka wywołuje funkcję handleCapture,
jako argument przekazując jej plik. W funkcji handleCapture najpierw sprawdzany jest typ pliku, a po
potwierdzeniu, że plik jest obrazkiem, tworzony jest nowy element image, który następnie jest dodawany
do strony, a do niego jest ładowany plik. Na rysunku 15.3 przycisk Wybierz plik został kliknięty kilka razy,
a uzyskane zdjęcia w postaci miniatur zostały dodane do elementu div, dzięki czemu można je oglądać.
368 Rozdział 15. Integracja z urządzeniami przenośnymi

Rysunek 15.3. Po zrobieniu zdjęcia urządzenie ponownie wyświetla stronę aplikacji


wraz z miniaturką zdjęcia

DeviceOrientation Event Specification


DeviceOrientation Event Specification powstała w grupie roboczej Device API, ale później przeniesiono
ją do grupy roboczej Geolocation, więc nie zobaczysz jej już na aktualnej liście grupy roboczej Device API.
Pozostawiliśmy ją w tym rozdziale, gdyż jest ona w szczególny sposób skoncentrowana na integracji
z wewnętrznym żyroskopem urządzenia, którego użytkownik w danym momencie używa. Wiele telefonów,
tabletów, a nawet komputerów ma wbudowany taki wewnętrzny żyroskop, a przeglądarki, takie jak Chrome,
mają już zaimplementowane odpowiednie zdarzenia w swoich javascriptowych silnikach.
Specyfikacja DeviceOrientation organizacji W3C opisuje trzy nowe zdarzenia, które zostały wymienione
poniżej:
„ deviceorientation — udostępnia kierunki w formacie alfa, beta, gamma, pokazujące położenie
przestrzenne urządzenia,
„ compassneedscalibration — generowane przez przeglądarkę zdarzenie, które stwierdza, że kompas
urządzenia wymaga kalibracji,
„ devicemotion — przekazuje informacje o przyspieszeniu, przyspieszeniu z uwzględnieniem
grawitacji, szybkości obrotu oraz odstępie czasowym pomiędzy kolejnymi wywołaniami zdarzenia.

Wszystkie trzy zdarzenia dotyczące położenia urządzenia są generowane na obiekcie window, a ich rejestracja
nie odbiega od rejestracji innych zwykłych zdarzeń na tym obiekcie. Zdarzenie deviceorientation poprzez
swoje dane przekazane do procedury obsługi udostępnia cztery możliwe do odczytu atrybuty: alpha, beta,
gamma i absolute. Właściwości alpha, beta i gamma odpowiadają ułożeniu urządzenia w trzech wymiarach.
Właściwość alpha reprezentuje obrót w płaszczyźnie poziomej, a beta i gamma odpowiadają nachyleniu
w lewo/prawo oraz w przód/tył. Na przykład laptop na biurku miałby beta równe 0 i gamma równe 0, podczas
gdy alpha miałaby wartość oznaczającą kierunek, w jakim jest ustawiony. W przypadku laptopa zorientowanie
w przestrzeni jest wyznaczane dla klawiatury, a nie ekranu, podczas gdy orientacja telefonu komórkowego
dotyczy samego telefonu. Właściwość absolute informuje, czy przeglądarka może podać bezwzględne wartości
Tworzenie poziomicy 369

kątów kierunków. Jeśli nie może, jej wartość będzie równa false. W większości naszych testów wartość ta
była rzeczywiście równa false, chociaż kąty beta i gamma miały wartości bezwzględne, a alpha miała arbitralną
wartość 0 podczas uruchamiania strony.
Zdarzenie compassneedscalibration jest proste i powiadamia okno, że wewnętrzny kompas urządzenia
wymaga kalibracji. Według specyfikacji zdarzenie to może zostać anulowane przez stronę internetową
lub aplikację, a domyślny interfejs użytkownika służący do kalibracji urządzenia może zostać zastąpiony
niestandardową kalibracją udostępnioną przez aplikację internetową. Jednak szczegóły dotyczące tego
zdarzenia nie są jeszcze ustalone.
Zdarzenie devicemotion jest bardziej skomplikowane, ponieważ zawiera atrybuty dotyczące przyspieszenia,
rotacji i interwału. Atrybut acceleration jest instancją nowego interfejsu, DeviceAcceleration, który
udostępnia atrybuty przyspieszenia x, y, z w formie typu danych double, pokazujące przyspieszenie
w kierunkach X, Y i Z. Szybkość rotacji jest również instancją interfejsu, ale interfejsu DeviceRotationRate,
który udostępnia dane o obrocie w postaci argumentów alpha, beta, gamma. Wszystkie atrybuty zdarzenia
devicemotion są w specyfikacji określone jako opcjonalne. Tabela 15.4 pokazuje atrybuty zdarzenia
devicemotion bardziej szczegółowo.

Tabela 15.4. Wartości atrybutów zdarzenia devicemotion

Atrybut Typ Opis


acceleration DeviceAcceleration Przyspieszenie urządzenia reprezentowane jako
wartości X, Y, Z.
accelerationIncludingGravity DeviceAcceleration Przyspieszenie urządzenia uwzględniające efekt
grawitacji, reprezentowane poprzez wartości X, Y, Z.
rotationRate DeviceRotationRate Obrót urządzenia pokazany za pomocą kątów
alpha, beta i gamma.
interval double Czas w milisekundach, co jaki są zbierane dane
o przyspieszeniu i obrocie. Powinna to być stała.

PRZEPIS DLA ŚREDNIO ZAAWANSOWANYCH


Tworzenie poziomicy
Jeśli miałeś okazję grania w gry na urządzeniach przenośnych, najprawdopodobniej widziałeś przykładową
natywną aplikację poziomicy, która jest dostępna na większości platform przenośnych i służy do pokazywania
położenia urządzenia. Możesz wykorzystać zdarzenie deviceorientation, aby udostępnić ten sam rodzaj
funkcji w JavaScripcie po stronie klienta dzięki sprawdzaniu pól beta i gamma zdarzenia. (Nie potrzebujesz
pola alpha, gdyż poziomica nie wykorzystuje informacji o obrocie urządzenia w płaszczyźnie poziomej).
W tym przepisie utworzysz prymitywną poziomicę, złożoną z ułożonych w warstwy elementów canvas,
oraz będziesz wyświetlał wartości beta i gamma, jak pokazano na rysunku 15.4.
Możesz poprawić algorytmy pozycjonowania, dodając funkcje, takie jak ograniczenie obszaru poruszania
się pęcherzyka do okręgu, ale w tym przepisie chcemy pokazać podstawy obsługi danych dotyczących położenia
urządzenia. Aby utworzyć poziomicę, użyj listingu 15.3 i wykonaj następujące czynności:
1. Utwórz pustą stronę HTML zawierającą trzy elementy canvas, które obejmują poziomicę,
i dodaj elementy span do wyświetlania wartości beta i gamma.
370 Rozdział 15. Integracja z urządzeniami przenośnymi

Rysunek 15.4. Przykładowy wynik pokazujący warstwy elementów canvas poziomicy


i wartości beta/gamma na przechylonym urządzeniu

2. Nadaj styl elementom canvas.


3. W znacznikach script dodaj funkcję init, która zawiera definicję procedury obsługi
zdarzenia deviceorientation.
4. Dodaj kod uruchamiający funkcję init po załadowaniu strony, wykorzystując
window.addEventListener.

Uwaga
Ten przepis wymaga od Twojej przeglądarki obsługi elementu canvas oraz tego, żeby urządzenie,
na którym uruchamiasz stronę, posiadało wewnętrzny żyroskop udostępniający wartości beta i gamma.

Listing 15.3. Tworzenie poziomicy z wartościami beta i gamma

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>15.3. Poziomica</title>
<style>
.canvas {
height:100px;
width:100px;
}
</style>
<script>

// zmienne płótna i kontekstu, służące do rysowania


var cvsBackground;
var ctxBackground;
var cvsBubble;
var ctxBubble;
var cvsCircle;
var ctxCircle;

// zmienne przechowujące wartości beta i gamma


var tempBeta = 0;
var tempGamma = 0;

// zmienne położenia i środka płótna


Tworzenie poziomicy 371

var canvasX;
var canvasY;
var canvasXCenter;
var canvasYCenter;

// inicjalizuj referencje obiektów płótna i procedury obsługi zdarzenia orientation


function init() {

// pobierz referencje do obiektów płótna i ich kontekstów


cvsBackground = document.getElementById('background');
ctxBackground = cvsBackground.getContext('2d');
cvsBubble = document.getElementById('bubble');
ctxBubble = cvsBubble.getContext('2d');
cvsCircle = document.getElementById('circle');
ctxCircle = cvsCircle.getContext('2d');

// ustaw współrzędne obiektów płótna i ich środków


canvasX = parseInt(cvsBackground.offsetLeft);
canvasY = parseInt(cvsBackground.offsetTop);
canvasXCenter = parseInt(cvsBackground.width/2);
canvasYCenter = parseInt(cvsBackground.height/2);

// narysuj tło poziomicy


ctxBackground.fillStyle='#0c0';
ctxBackground.beginPath();
ctxBackground.arc(canvasXCenter,canvasYCenter,50,0,Math.PI*2,true);
ctxBackground.closePath();
ctxBackground.fill();

// narysuj pęcherzyk
ctxBubble.fillStyle='#0FF';
ctxBubble.beginPath();
ctxBubble.arc(canvasXCenter,canvasYCenter,10,0,Math.PI*2,true);
ctxBubble.closePath();
ctxBubble.fill();

// narysuj środkowy okrąg, służący do wyznaczenia poziomu


ctxCircle.strokeStyle='#fff';
ctxCircle.lineWidth='5';
ctxCircle.beginPath();
ctxCircle.arc(canvasXCenter,canvasYCenter,20,0,Math.PI*2,true);
ctxCircle.closePath();
ctxCircle.stroke();

// dodaj obiekt nasłuchujący zdarzeń deviceorientation


window.addEventListener('deviceorientation', function(event) {

// sprawdź, czy beta albo gamma się zmieniły


if (parseInt(event.beta)!=tempBeta||parseInt(event.gamma)!=tempGamma) {

// ustaw zmienne zawierające wartości beta i gamma


tempBeta = parseInt(event.beta);
tempGamma = parseInt(event.gamma);

// ustaw zmienne korekty


var adjX = 0;
var adjY = 0;
var adjFactor = 3; // czynnik skalujący dla ruchu pęcherzyka
var adjMax = 40; // maksymalna korekta dla pęcherzyka
372 Rozdział 15. Integracja z urządzeniami przenośnymi

// określ nowe położenie pęcherzyka


if (tempBeta*adjFactor > adjMax) {
adjY = -adjMax;
} else if (tempBeta*adjFactor < -adjMax) {
adjY = adjMax;
} else {
adjY = tempBeta*adjFactor * -1;
}
if (tempGamma*adjFactor > adjMax) {
adjX = -adjMax;
} else if (tempGamma*adjFactor < -adjMax) {
adjX = adjMax;
} else {
adjX = tempGamma*adjFactor * -1;
}

// przesuń pęcherzyk na nowe miejsce


cvsBubble.style.left = canvasX+adjX+'px';
cvsBubble.style.top = canvasY+adjY+'px';

// ustaw wartości elementów span, pokazujących beta i gamma


var spanBeta = document.getElementById('betaValue');
var spanGamma = document.getElementById('gammaValue');
spanBeta.innerHTML = tempBeta;
spanGamma.innerHTML = tempGamma;

}
}, true);
}

// wywołaj funkcję init po załadowaniu strony


window.addEventListener('load',init,false);

</script>
</head>
<body>
<h1>Rozdział 15. Poziomica</h1>
<!-- Elementy canvas są ułożone w warstwy według poniższej definicji -->
<canvas id="background" style="position:absolute; left:100px; top:150px;">
Tło poziomicy.
</canvas>
<canvas id="bubble" style="position:absolute; left:100px; top:150px;">
Pęcherzyk poruszający się i pokazujący poziom.
</canvas>
<canvas id="circle" style="position:absolute; left:100px; top:150px;">
Nałożone okręgi pokazujące środek.
</canvas>
<br>
Wartość beta: <span id="betaValue"></span><br>
Wartość gamma: <span id="gammaValue"></span>
</body>
</html>

Po załadowaniu strony do przeglądarki i wywołaniu funkcji init skrypt najpierw utworzy różne składniki
poziomicy. Poziomnica składa się z trzech elementów canvas: tła, pęcherzyka oraz znajdującego się na
pierwszym planie środkowego pierścienia. Cel podzielenia poziomicy na te trzy składniki jest dwojaki. Po
pierwsze, umożliwia ułożenie warstwowe z pęcherzykiem znajdującym się ponad tłem, ale pod środkowym
Podsumowanie 373

pierścieniem, aby wyglądało to jak w prawdziwej poziomicy. Po drugie, pozwala Ci to programowo przesuwać
pęcherzyk, bez konieczności odrysowywania pozostałych elementów przy każdym ruchu.
Po narysowaniu składników poziomicy skrypt ustawia procedurę obsługi zdarzenia deviceorientation.
Funkcja obsługi zdarzenia najpierw sprawdza, czy doszło do jakiejkolwiek zmiany wartości beta lub gamma,
a następnie przypisuje zmiennym tymczasowym nowe wartości, jeśli taka zmiana zaistniała. Następnie
funkcja ustawia pewne zmienne korekty pozycji, które są używane w obliczeniach dotyczących położenia
płótna pęcherzyka. Zmienna adjFactor służy do „przyspieszenia” ruchu, podczas gdy zmienna adjMax
określa maksymalną odległość, o jaką pęcherzyk można przesunąć. Po obliczeniu nowych współrzędnych
górnej i lewej krawędzi płótna pęcherzyka płótno jest przesuwane i wyświetlane są zaktualizowane kąty beta
i gamma. Zauważ, że w przypadku kątów beta i gamma używamy jedynie całkowitych części wartości typu
double ze zdarzenia. Kąty przekazywane w naszych testach są niezmiernie dokładne, więc chcąc zapobiec
przesuwaniu płótna przy najdrobniejszych zmianach kąta, zdecydowaliśmy się przesuwać go tylko wtedy,
gdy nastąpiła zmiana o cały stopień.

Podsumowanie
W tym rozdziale poznałeś kilka nowych i jeszcze tworzonych specyfikacji API oraz zdarzeń, których celem
jest zapewnienie dostępu do wszelkiego rodzaju informacji i funkcji urządzeń. Ukończenie, publikacja
i udostępnienie ich do użytku w różnych przeglądarkach jest tylko kwestią czasu. Aplikacje internetowe będą
w końcu miały dostęp do funkcji urządzeń, które teraz są wykorzystywane tylko w natywnych aplikacjach.
Wiele specyfikacji dotyczących API urządzeń pozostaje jeszcze nieukończonych, ale postępy prac wykazują,
że możliwości HTML5 ciągle się zwiększają.
374 Rozdział 15. Integracja z urządzeniami przenośnymi
Przepisy

PRZEPISY DLA POCZĄTKUJĄCYCH


Budowa startowego dokumentu HTML5 . ....................................................................26
Wykorzystanie elementu header do utworzenia nagłówka witryny ...................................29
Wykorzystanie elementu hgroup do grupowania nagłówków ...........................................31
Tworzenie nawigacji przy użyciu elementu nav . ............................................................31
Użycie elementu article . ............................................................................................34
Tworzenie paska bocznego za pomocą elementu aside ................................................37
Użycie elementu footer . ............................................................................................39
Oznaczanie rysunków i ich podpisów elementami figure i figcaption ...............................51
Oznaczanie daty i czasu za pomocą elementu time ......................................................53
Tworzenie widżetu przełącznika za pomocą elementu details ........................................54
Użycie elementu address do danych kontaktowych .......................................................56
Podświetlanie tekstu za pomocą elementu mark ..........................................................57
Użycie elementu s do pokazania niepoprawnej lub nieistotnej treści ..............................58
Opakowywanie elementów łączami . ...........................................................................65
Obsługa Internet Explorera . .......................................................................................73
Testowanie działania nowych możliwości HTML5 . ........................................................75
Korzystanie z własnych czcionek przy użyciu @font-face ................................................92
Tworzenie formularza do danych kontaktowych ..........................................................109
Tworzenie formularza wyszukiwania przy użyciu input type="search" ............................112
Tworzenie kontrolek kalendarza i czasu . ...................................................................113
Tworzenie pola do wprowadzania liczby . ...................................................................116
Tworzenie suwaka (bez potrzeby użycia JavaScriptu) ...................................................117
Tworzenie próbnika kolorów . ...................................................................................117
Wyświetlanie wyników za pomocą elementu output ....................................................118
376 Przepisy

Użycie tekstu zastępczego formularza . .....................................................................119


Autouzupełnianie za pomocą atrybutu list i elementu datalist ......................................120
Śledzenie postępu wykonania zadania za pomocą elementu progress .........................121
Wskazywanie wyniku pomiaru za pomocą elementu meter ..........................................122
Przechodzenie do elementu form po załadowaniu strony .............................................123
Zezwalanie na wiele wartości . .................................................................................124
Prosta walidacja przy użyciu atrybutu required ............................................................125
Nakładanie ograniczeń na wprowadzane dane ...........................................................126
Nałożenie siatki na płótno . ......................................................................................135
Tworzenie prostych kształtów i linii . .........................................................................139
Dodawanie tekstu . .................................................................................................147
Wstawianie obrazka . ...............................................................................................148
Dołączanie filmów za pomocą elementu video ...........................................................163
Umieszczanie dźwięku za pomocą elementu audio .....................................................185
Dodawanie do historii wpisów za pomocą pushState ..................................................204
Tworzenie przeglądarki obrazków . ............................................................................207
Zmiana historii za pomocą replaceState . ..................................................................211
Ustalanie położenia geograficznego za pomocą zwykłego wywołania
getCurrentPosition . ...............................................................................................225
Pobieranie i ustawianie danych w pamięci sesji .........................................................250
Nadawanie stylów z pamięci sesji . ...........................................................................253
Rozmawianie przez gniazda sieciowe . ......................................................................280
Tworzenie wątku roboczego . ....................................................................................285
Przeciąganie i upuszczanie pomiędzy elementami div .................................................299
Tworzenie pliku manifestu . ......................................................................................310
Korzystanie ze stron internetowych offline .................................................................312
Wyświetlanie prostego powiadomienia ......................................................................318
Odczyt atrybutów pliku . ...........................................................................................332
Przetwarzanie wielu plików za pomocą przeciągania i upuszczania ...............................334
Pobieranie wszystkich kontaktów i numerów telefonu .................................................360

PRZEPISY DLA ŚREDNIO ZAAWANSOWANYCH


Grupowanie treści przy użyciu elementu section ...........................................................35
Wykorzystanie narzędzia HTML5 Outliner do utworzenia prawidłowej
struktury dokumentu . ..............................................................................................41
Dodawanie informacji semantycznych za pomocą mikrodanych .....................................66
Przepisy 377

Stosowanie WAI-ARIA z HTML5 . .................................................................................67


Wykorzystanie jQuery do zastąpienia kalendarza ..........................................................77
Wykorzystanie biblioteki Modernizr do wykrywania możliwości .......................................79
Tworzenie dostosowującego się projektu za pomocą CSS3 Media Queries .....................85
Tworzenie przycisków za pomocą gradientów CSS i wielu teł .........................................95
Upiększanie witryny za pomocą transformacji i przejść ..................................................98
Pisanie własnych reguł walidacji . .............................................................................126
Dostosowywanie formularza i nadawanie mu stylu .....................................................128
Rysowanie wielokątów za pomocą ścieżki . ................................................................144
Rysowanie łuków i okręgów . ....................................................................................146
Przycinanie obrazka . ...............................................................................................149
Animacja mapy sprite’ów . .......................................................................................150
Włączanie wideo we wszystkich przeglądarkach .........................................................166
Tworzenie wideo z napisami . ...................................................................................172
Umieszczanie dźwięku we wszystkich przeglądarkach .................................................186
Tworzenie miksera . ................................................................................................190
Pobieranie stanu w przeglądarce obrazków . ..............................................................209
Zmiana historii strony . ............................................................................................213
Testowanie bezpieczeństwa historii . ........................................................................218
Wyświetlanie na mapie informacji o lokalizacji przy użyciu getCurrentPosition ...............228
Określanie odległości za pomocą opcji lokalizacji .......................................................233
Zapisywanie formularzy za pomocą lokalnego przechowywania danych .........................257
Dodawanie dwukierunkowej komunikacji . .................................................................288
Podgląd obrazków przy użyciu readAsDataURL ...........................................................338
Przechwytywanie obrazu za pomocą elementu input typu file .......................................365
Tworzenie poziomicy . ..............................................................................................369

PRZEPISY DLA ZAAWANSOWANYCH


Wykorzystanie wszystkich nowych elementów do utworzenia strony z wiadomościami .....43
Wykorzystanie wszystkich nowych elementów do utworzenia strony
z wynikami wyszukiwania . ........................................................................................46
Oznaczanie komentarzami strony z artykułem . ............................................................69
Tworzenie animacji za pomocą CSS-a . ......................................................................103
Wszystko razem — tworzenie formularza rejestracyjnego ............................................130
Animowanie obrazka . ..............................................................................................154
Animacja pionowego wykresu słupkowego .................................................................157
378 Przepisy

Tworzenie niestandardowych kontrolek . ....................................................................176


Dodawanie internetowego radia . ..............................................................................194
Używanie zaawansowanych obiektów danych stanu do przenoszenia informacji
pomiędzy stronami . ..............................................................................................215
Podążanie za poruszającym się obiektem dzięki watchPosition ....................................239
Przechwytywanie zdarzeń w pamięci lokalnej ..............................................................261
Użycie sieciowej bazy danych do stworzenia listy zakupów ..........................................270
Wykorzystywanie współdzielonych wątków roboczych ..................................................291
Wykorzystanie zdarzeń i obiektu dataTransfer ............................................................302
Tworzenie strony powiadomień o tweetach . ..............................................................322
Parsowanie pliku CSV za pomocą readAsText . ..........................................................342
Tworzenie lokalnego pliku . ......................................................................................350
Skorowidz

src, 169, 188


A step, 126
C
adresy e-mail, 351 type, 364 ciasteczka, 247
aktualizacja pamięci podręcznej, 312 atrybuty Contacts API, 359
Android, 91 elementu audio, 187 CSS, 74
animacja, 103 interfejsu ContactField, 362 dla ekranów, 89
mapy sprite’ów, 150 interfejsu Contacts, 360 dla smartfonów, 90
wykresu, 157–162 obiektu adresowego, 232
CSS3, 85
animowana zmiana koloru, 101 pliku, 332
CSS3 Media Queries, 85
animowanie obrazka, 101, 154 storageEvent, 261
czcionki, 92, 95
animowany baner, 104 wideo, 169
czyszczenie pamięci, 260
API dla multimediów, 175, 189 autouzupełnianie, 120
API HTML5, 220
arkusz stylów smartphone.css, 86 D
artykuł z komentarzami, 71 B
atrybut baza danych, 270 dane formularza, 257
accept, 365 bezpieczeństwo, 310 deklaracja
address, 232 danych, 248 @font-face, 92
audio, 170 File API, 331 dropzone, 298
autofocus, 123 historii, 218 Device APIs Working Group, 357
autoplay, 170, 188 DeviceOrientation Event Specification,
biblioteka Modernizr, 79
capture, 365 368
biblioteki, 220
controls, 171, 189
bloki try-catch, 253 dodawanie
draggable, 297, 299
błąd informacji semantycznych, 66
dropzone, 298, 301
CONSTRAINT_ERR, 270 internetowego radia, 194
formnovalidate, 127
DATABASE_ERR, 269 pliku .js, 294
kind, 172
PERMISSION_DENIED, 231 tekstu, 147
list, 120
POSITION_UNAVAILABLE, 231 treści zastępczej, 186
loop, 170, 188
QUOTA_ERR, 270 wątku roboczego, 290
manifest, 310
SYNTAX_ERR, 270 wpisów do historii, 204
multiple, 124
TIMEOUT, 231 DOM, 73, 284
novalidate, 127
TIMEOUT_ERR, 270 dostęp do danych, 248
pattern, 126
TOO_LARGE_ERR, 269 Drag and Drop API, 297
placeholder, 119
UNKNOWN_ERR, 269 DTD, Document Type Definition, 26
port, 295
VERSION_ERR, 269
poster, 169
błędy
preload, 170, 188
readyState, 280 interfejsu FileReader, 343
required, 125 Messaging API, 364
380 Skorowidz

showFile, 342
E F showGrid, 136, 138
efekt hover, 100 FIFO, first in, first out, 218 showImage, 207
efekty, 140 File API, 331, 349, 355 startWatch, 244
gradientu, 96 Directories and System, 349 startWorkerThread, 287
przejścia, 101 Writer, 349, 355 swapCache, 316
element film, 163 towerHandleDragOver, 308
abbr, 64 filtrowanie plików, 334 wywołania zwrotnego, 229
address, 56 flaga trwałości, 354
article, 34, 36 formatowanie
aside, 37 kwadratu, 141
G
audio, 185 napisów, 174 generator czcionek, 94
b, 63 formaty plików, 93 geokodowanie, 224
canvas, 76, 133, 153 formularz, 108 Geolocation API, 75, 223, 245
cite, 58 kontaktowy, 109, 313 gniazdo sieciowe, 279
datalist, 120 rejestracyjny, 129–132 Google Font Directory, 94
del, 58 z obliczeniami, 313 gra Wieże Hanoi, 303, 307
details, 54 funkcja gradient liniowy, 96
div, 37 animateSprite, 152 gradienty, 95, 141
dl, 61 blockHandleDragStart, 307 gradienty z zatrzymaniami, 97
doctype, 26 calculateDistance, 234 grupowanie
em, 64 changeField, 260 nagłówków, 31
figcaption, 53 checkStorage, 260 treści, 35
figure, 51 clearFile, 342
footer, 39 clearRect, 146
form, 123, 128 displayFiles, 337 H
header, 29 dragStartHandler, 301
historia
hgroup, 31 drawImage, 149
przeglądarki, 203
hr, 64 fileAction, 351, 354
sesji, 203
i, 64 geoSuccess, 231
strony, 213
image, 365 getGroceryItems, 276
History API, 203
input, 77 getResult, 329
HTML, Hypertext Markup Language,
input typu file, 365 grabTweets, 328
13
link, 27 growBars, 162
HTML Media Capture, 365
mark, 57 handleCapture, 367
HTML5, 15
meter, 122 handleDragOver, 337
HTML5 Boilerplate, 75
nav, 31 handleFileDrop, 337
HTML5 Outliner, 41
ol, 59 initGraph, 161
HTML5Shiv, 74
output, 118 initSettings, 161
progress, 121 loadPages, 214
s, 58 loadStations, 200 I
script, 27 moveGear, 156
section, 34–37, 62 nextState, 211 identyfikator stateInfo, 210
small, 63 onLoadStartHandler, 347 implementacja historii, 205
source, 166 onOpen, 283 informacje o
strong, 64 onStorageEvent, 266 kontakcie, 359
time, 53 remove, 355 lokalizacji, 228
track, 172 reverseGeoCode, 234 położeniu geograficznym, 223
video, 163, 169, 171 sendNumber, 295 przeglądarce, 84
elementy setLocation, 234 interfejs
listy, 108 setPermission, 320 BlobBuilder, 349
usunięte, 64 setStation, 200 DataTransfer, 298, 301
Skorowidz 381

device, 363 komunikaty dbPresent, 276


DirectoryEntry, 349 błędów, 129 deleteItem, 277
Entry, 349 walidacji, 110 drawImage, 149, 151
File, 331 konfiguracja executeSql, 269
FileEntry, 349 biblioteki Modernizr, 80 fillText, 138, 147
FileList, 331, 334 elementu canvas, 134 find, 359
FileReader, 331, 337 konspekt dokumentu, 42 fireNotification, 320
FileReaderSync, 338 kontakty, 359 getContext, 76, 134
FileSaver, 349 kontrolka wprowadzania daty, 77 getCurrentPosition, 224, 227, 234
FileSystem, 349 kontrolki getFile, 354
FileWriter, 349 domyślne, 182 getGroceryItems, 276
Notification, 317 formularzy, 108 history.pushState, 205
NotificationCenter, 316 niestandardowe, 176 importScripts, 329
SharedWorker, 291, 296 wideo, 177 initShoppingDb, 276
Storage, 249 kroje pisma, 94 key, 258
Worker, 284 Modernizr.load, 83
onaborthandler, 348
L onErrorHandler, 348
J openDatabase, 268, 276
liczba
JavaScript, 73 par klucz-wartość, 258 postMessage, 284, 288
jQuery, 77, 78 wizyt, 251 preventDefault, 301, 309
JSON, 288 licznik idxCounter, 211 pushState, 204
lista readAsDataURL, 338–342
JSON, 200 readAsText, 342
K nieuporządkowana, 108 readTransaction, 276
uporządkowana, 59 removeItem, 253, 260
kadry animacji, 157 replaceState, 211
klasa zakupów, 272
listy zagnieżdżone, 60 requestFileSystem, 351
no-webgl, 80 requestPermission, 320
no-sessionstorage, 81 logo HTML5, 17
lokalizacja, 223, 225, 233 rotate, 154
sessionstorage, 81 scale, 154
klucz lokalne przechowywanie danych, 257,
262 setItem, 250
themeColor, 257 show, 317, 322
visits, 252 lokalny plik, 350
showGroceryItems, 276
kodek strokeText, 147
MP3, 186 Ł terminate, 284
Ogg Vorbis, 186 transaction, 269, 276
kodeki wideo, 164 ładowanie skryptów, 83 translate, 154
kodowanie znaków, 26 łącza, 65 watchPosition, 239, 244
kolor webkitRequestFileSystem, 351, 354
motywu, 256 window.addEventListener, 136
tła, 253 M window.openDatabase, 276
komentarz w pliku, 312 manifest aplikacji obliczającej, 314 metody
komentarze, 69 mapa sprite’ów, 151 DataTransfer, 298
kompatybilność przeglądarek, 318 Messaging API, 363 File API, 350
komunikacja metoda FileReader, 338
dwukierunkowa, 288 abort, 343 mikrodane, 66
z serwerami, 279 cancel, 317 mikroformaty, 67
komunikat clear, 260, 267 mikser, 190, 194
błędu, 125, 200, 351 clearWatch, 239 motyw domyślny, 253
do klientów, 296 createNotification, 317, 320, 322
END, 328 createPattern, 150
TWEET, 328
382 Skorowidz

elementu
N canvas, 370
P
nadawanie stylu, 141, 253 datalist, 120 pamięć
nagłówek, 30 output, 119 lokalna, 258, 261
CACHE, 311 progress, 122 podręczna, 309, 312
FALLBACK, 311 video, 164 sesji, 250, 252
NETWORK, 312 File API, 332 para klucz-wartość, 248, 266
napisy wideo, 172, 173 gradientów, 95 parametry
narzędzia History API, 204 funkcji find, 359
Google Chrome, 252 Internet Explorera, 73 metody
płótna, 139 iPhone’a, 91 executeSql, 269
narzędzie pamięci podręcznej, 309 getCurrentPosition, 225
@font-face Generator, 93 pola wprowadzania liczby, 116 openDatabase, 268
HTML5 Outliner, 41 próbnika kolorów, 118 transaction, 269
nawigacja, 31 przejść, 98 watchPosition, 239
nazwy klas, 28 transformacji, 98 parsowanie
Network Information API, 364 typu wejściowego pliku, 348
notacja z kropką, 276 datetime, 113 pliku CSV, 342
notatki, 262 email, 110 tweetów, 326
Notification API, 316, 317 search, 112 pasek
tel, 111 boczny, 37
url, 111 postępu, 122
O wątków roboczych, 285 przewijania, 183
WebSocket API, 280 plik
obiekt
zapytań o media, 86 CSV, 342, 347
ApplicationCache, 315
zdarzenia onmessage, 287 httpd.conf, 168
Blob, 351
zdarzenia popstate, 210 lokalny, 351
coords, 227
zdarzeń, 266 manifestu, 309, 310
danych stanu, 215 współdzielonego wątku
odczyt atrybutów pliku, 332
dataTransfer, 302 roboczego, 294
odnajdywanie lokalizacji, 225
FileEntry, 355 pliki
odświeżanie pamięci podręcznej, 310
FileReader, 342 .appcache, 310
odtwarzacz
fileWriter, 355 .eot, 93
Flash Player, 186
localStorage, 249, 260 .htm, 27
JW Playera, 186
lokalizacji, 227 .js, 294
radia internetowego, 194
sessionStorage, 249 .mov, 165
odtwarzacze
storageEvent, 261 .mp3, 185
dźwięku, 185
sworker, 295 .mp4, 166
wideo, 183
timestamp, 227 .ogv, 166
odwrócone geokodowanie, 224
WebSocket, 279 .otf, 93
offline, 313
WindowDatabase, 268 .svg, 93
określanie odległości, 233
obrazek, 148 .ttf, 93
opakowywanie łączy, 65
obsługa .vtt, 172
opcje
@font-face, 92 .webm, 166
CSS-a, 81
animacji, 103, 104 .woff, 93
lokalizacji, 233
atrybutu dźwiękowe, 189
osadzanie
autofocus, 123 płótno, 135, 153
dźwięku, 186
formnovalidate, 127 pobieranie
wideo, 167
multiple, 124 kontaktów, 360
oznaczanie
pattern, 126 numerów telefonów, 361
daty i czasu, 53
required, 125 stanu z historii, 209
rysunków, 51
step, 127 tweetów, 328
wiadomości, 34
błędów, 231, 269, 276, 354 z historii, 213
Skorowidz 383

podgląd obrazków, 338 transformacje CSS, 99


podświetlanie tekstu, 57
S treści nieistotne, 58
pokazywanie motywu, 256 siatka, 135 tweety, 322
polecenia SQL, 268 sieciowe bazy danych, 277 tworzenie
polyfilling, 82 składnia, 27 animacji, 103
połączenie z gniazdem, 279 skrypt bazy danych, 270
położenie geograficzne, 225, 232, 238 Modernizr, 80 formularza, 109
port klienta, 296 MooTools, 172 formularza rejestracyjnego, 130
powiadomienia, 318 słownik, 61 formularza wyszukiwania, 112
powiadomienia o tweetach, 322 specyfikacja gradientów, 141
poziomica, 369 Contacts API, 359 konspektu dokumentu, 42
poziomy uprawnień, 323, 328 DeviceOrientation, 368 kontrolek kalendarza, 113
prezentacja slajdów, 215 File API, 349 listy zakupów, 272
próbnik kolorów, 117 HTML5, 15 lokalnego pliku, 350
przechowywanie Microdata, 66 mikrodanych, 67
danych, 247, 257 WAI-ARIA, 67, 69 miksera, 190
w pamięci lokalnej, 257, 262 Web SQL Database API, 268 nawigacji, 31
przechwytywanie Web Storage, 249 niestandardowych kontrolek, 176
błędu, 253 strefa upuszczania, 309 paska bocznego, 37
obrazu, 365 strona pliku manifestu, 310
zdarzeń, 261 startowa, 28 pojemnika, 292
zdarzeń pamięci, 266 z wiadomościami, 43 połączenia, 279, 281
przeciąganie z wynikami wyszukiwania, 46, 49 powiadomień, 318, 323
obiektu, 297, 299, 302 zastępcza, 314 poziomicy, 369, 370
plików, 334 struktura dokumentu, 41 próbnika kolorów, 117
przeglądarka obrazków, 207, 209 styl formularza, 128 przeglądarki obrazków, 207
przeglądarki, 73 szablony, 75 przycisków, 95
przejścia, 98 słownika, 61
przekształcenia płótna, 153 strony klienta, 288, 292
przełącznik, 54
Ś strony powiadomień, 322
przenoszenie informacji, 215 strony z wiadomościami, 43
ścieżki, 144
przesyłanie wiadomości do wątku, 285 suwaka, 117
śledzenie
przycinanie obrazka, 150 wątku roboczego, 285
położenia, 239, 240
przycisk Zastąp stan, 212 wideo z napisami, 172
przepływu danych, 283
widżetu przełącznika, 54
tweetów, 326
zwijalnego spisu treści, 56
R typ
T datetime-local, 113
radio internetowe, 194 MIME, 168, 310, 347
rodzaje tabela wejściowy
połączeń, 364 departments, 271 date, 114
składowania, 248 groceryitems, 270 datetime, 113
role, 68 tablica email, 109
rysowanie connections, 296 month, 115
krzywych, 147 zgodności przeglądarek, 84 number, 116
linii, 142 tekst, 147 range, 117
łuku, 146 tekst zastępczy formularza, 119 search, 112
prostokąta, 139 testowanie tel, 110
siatki płótna, 136 HTML5, 84 time, 114
ścieżek, 142 obsługi geolokalizacji, 76 url, 111
trójkąta, 143 transakcja, 269 week, 115
wielokąta, 144 transformacje, 98
384 Skorowidz

U wtyczka VideoSub, 172 Drag and Drop API, 302


wykres słupkowy, 157, 161 durationchange, 201
układ wykrywanie FileReader, 338
domyślny ekranu, 88 CSS3, 79 wątków roboczych, 284
strony, 25, 45 możliwości HTML5, 81 WebSocket, 280
witryny z rolami, 69 typów, 79 zdarzenie
współrzędnych, 135 wyłudzanie informacji, 219 compassneedscalibration, 369
uprawnienia, 317, 323 wynik devicemotion, 369
upuszczanie pomiaru, 122 deviceorientation, 368
obiektu, 299 wyszukiwania, 46 domReady, 209
plików, 337 wyrażenia regularne, 126 dragover, 337
ustawianie wysyłanie error, 288
elementów w CSS-ie, 74 danych, 281 hashChange, 221
motywu, 253 komunikatów, 363 message, 328
obliczeń, 314 onchange, 260, 341
wiadomości, 279 onclick, 191
W wyświetlanie onconnect, 295
adresu URL, 218 ondragstart, 301
WAI-ARIA, 67
atrybutów pliku, 332 onerror, 284
walidacja, 107, 126
liczby wizyt, 251 onload, 339
wątek główny, 323
pliku CSV, 344 onmessage, 284
wątek roboczy, 284
powiadomień, 318–321 popstate, 209, 214, 218
dedykowany, 284
tweetów, 322 pushState, 210
współdzielony, 284
wyników, 118 SharedWorker, 291
Web Notifications API, 316
wywołanie REST, 323 window.addEventListener, 250
Web SQL Database API, 267, 271
zmiana
Web Storage API, 267
historii, 211
Web Workers API, 283 Y historii strony, 213
WebSocket API, 279
YouTube, 167 manifestu, 315
wiadomości, 35
zawartości adresu URL, 218
wideo, 167
zmienna dropzone, 337
z kontrolkami, 183
z napisami, 172
Z znacznik
body, 136
widżet kalendarza, 82 zapisywanie canvas, 136
wielokąt, 146 formularzy, 257 czasu, 285
wielowypełniacz, 83 w historii, 213 znaczniki nawigacyjne, 32
witryna Font Squirrel, 94 w localStorage, 258 znak hash, 205
wizualizacja stanu przeglądarek, 84 zapytania o media, 85–91 zwijalny spis treści, 56
własne reguły walidacji, 126 zarządzanie powiadomieniami, 320
właściwości Modernizr, 79 zastępowanie stanu, 212
właściwość zatrzymania, 97 Ż
transform, 100 zawartość pamięci, 252
transition, 100 zbiór API, 358 żądanie REST, 329
wpis do historii, 205, 214 zdarzenia żyroskop, 370
wprowadzanie liczb, 116 addEventListener, 182
współdzielony wątek roboczy, 291 API, 175, 189
wstawianie obrazka, 148 ApplicationCache, 315

You might also like