Rozdział 12. Manipulowanie historią dla zabawy i pożytku

10 listopada 2013
1 gwiadka2 gwiazdki3 gwiazdki4 gwiazdki5 gwiazdek

Do rzeczy

Pasek adresu przeglądarki to chyba najpopularniejszy na świecie element interfejsu programu, który dostał się do kultury masowej. Adresy URL można spotkać na billboardach, ścianach pociągów, a nawet na murach w postaci graffiti. Dzięki przyciskowi cofającemu do poprzedniej strony — bez wątpienia najważniejszemu elementowi przeglądarki — można z łatwością przeglądać w przód i w tył niezmierzone obszary internetu.

API history HTML5 jest standardem umożliwiającym manipulowanie historią przeglądarki za pomocą skryptów. Część tego API — historia nawigacji — była dostępna już w poprzednich wersjach języka HTML. Do nowości HTML5 należą możliwość dodawania elementów do historii przeglądarki, zmieniania adresu URL w pasku adresu (bez konieczności odświeżania strony) oraz zdarzenie zgłaszane w momencie usuwania wpisów z historii poprzez naciśnięcie przez użytkownika przycisku cofnięcia. Dzięki temu adres URL w pasku adresu przeglądarki może nadal pełnić rolę unikatowego identyfikatora dla bieżącego zasobu nawet w aplikacjach zawierających dużo skryptów, które nigdy nie odświeżają strony w całości.

Po co to robić

api history html5

Po co ktoś miałby ręznie manipulować adresami? Przecież, aby przejść pod nowy adres URL, wystarczy użyć prostego odnośnika. Tak się robi już od ponad 20 lat istnienia sieci. I długo jeszcze tak będzie. API, o którym mowa nie jest próbą pogrążenia sieci. Wręcz przeciwnie. Ostatnio programiści znaleźli wiele ciekawych sposobów na pogrążenie internetu bez pomocy jakichkolwiek nowych standardów. API historii HTML5 ma sprawić, że adresy URL będą przydatne także w aplikacjach sieciowych w dużym stopniu opartych na działaniu skryptów.

Wróćmy na chwilę do podstaw. Do czego służy adres URL? Jest niepowtarzalnym identyfikatorem zasobu. Można utworzyć do niego bezpośredni odnośnik, zapisać go w zakładkach, wyszukiwarki internetowe mogą go zaindeksować, można go komuś wysłać e-mailem, aby ten ktoś go kliknął i obejrzał polecany mu materiał. Krótko mówiąc adresy URL są bardzo przydatne.

Chcemy, aby unikalne zasoby były identyfikowane przez niepowtarzalne adresy URL, ale przeglądarki od zawsze miały pewne fundamentalne ograniczenie: gdy zmieni się adres URL, nawet poprzez skrypt, następuje kontakt z serwerem sieciowym i pełne odświeżenie strony. To wymaga czasu i pochłania zasoby oraz jest zwykłym marnotrawstwem gdy przeglądane kolejne strony są do siebie bardzo podobne. Podczas odświeżania strony pobierane jest wszystko, także te części, które są identyczne jak na poprzedniej stronie. Nie da się nakazać przeglądarce zmiany adresu URL i pobrania tylko połowy strony.

Ale pozwala na to API historii HTML5. Za pomocą skryptu można sprawić, że zamiast odświeżenia całej strony przeglądarka pobierze tylko jej wybraną część. Nie jest to jednak takie łatwe i wymaga trochę pracy. Patrzysz uważnie?

magik wykonujący sztuczkę karcianą

Powiedzmy że mamy dwie strony: A i B. Są one w 90% identyczne, a więc różni je tylko 10% treści. Użytkownik wchodzi na stronę A, a następnie przechodzi na stronę B. Zamiast dopuścić do pełnego odświeżenia strony, przerywamy proces nawigacji i ręcznie wykonujemy następujące czynności:

  1. Ładujemy 10% treści ze strony B, która jest inna niż na stronie A (najpewniej przy użyciu obiektu XMLHttpRequest). To wymaga paru poprawek w skryptach serwerowych aplikacji. Trzeba napisać kod zwracający te 10% strony B, które są inne niż na stronie A. Może to być ukryty adres URL albo parametr zapytania normalnie niewidoczny dla użytkownika końcowego.
  2. Wstawiamy nową treść w miejsce starej (przy użyciu innerHTML albo innej metody DOM). Dodatkowo może być konieczne wyzerowanie procedur obsługi zdarzeń elementów w podmienianej treści.
  3. Zmieniamy adres w pasku adresu przeglądarki na adres URL strony B. W tym celu używamy metody z API historii HTML5, o której piszę nieco dalej.

Na koniec (jeśli wszystko zrobiono poprawnie), w przeglądarce jest struktura DOM identyczna ze strukturą strony B, tak jakbyśmy normalnie na tę stronę przeszli. W pasku adresu przeglądarki znajduje się adres URL strony B, też tak jakbyśmy na tę stronę przeszli w normalny sposób. Ale w rzeczywistości nie przeszliśmy na tę stronę i nie dokonaliśmy pełnego odświeżenia strony. To wszystko to tylko iluzja. Ponieważ jednak złożona przez nas strona wygląda dokładnie tak, jak strona B i ma taki sam jak ona adres URL, użytkownik nie zauważy różnicy (ani nie doceni naszej ciężkiej pracy, aby żyło mu się lepiej).

Jak to robić

API historii HTML5 to zestaw metod obiektu window.history i jedno zdarzenie obiektu window. Można je wykorzystać do sprawdzenia obsługi API historii przez przeglądarkę. Obecnie API te jest obsługiwane tylko przez kilka najnowszych przeglądarek, przez co opisywane tu techniki należy stosować w połączeniu z metodą „stopniowego ulepszania”.

Obsługa metody history.pushState
IEFirefoxSafariChromeOperaiPhoneAndroid
·4.0+5.0+8.0+11.50+4.2.1+·

Prostym, ale wcale nie banalnym przykładem wykorzystania API historii HTML5 jest strona dive into dogs . Demonstruje typową stronę: długi artykuł z galerią zdjęć. W nowoczesnej przeglądarce kliknięcie odnośników Next i Previous w galerii spowoduje zmianę zdjęcia na miejscu i aktualizację adresu URL w pasku adresu przeglądarki, ale bez odświeżania całej strony. W mniej zaawansowanych przeglądarkach — albo zaawansowanych tylko z wyłączoną obsługą skryptów — odnośniki te działają, jak zwykłe łącza, czyli powodują otwarcie i pełne odświeżenie nowej strony.

To prowadzi do ważnej konkluzji:

Zdaniem profesora Kodeckiego

Jeśli twoja aplikacja nie będzie działać w przeglądarkach z wyłączoną obsługą skryptów, pies Jakoba Nielsena przyjdzie do waszego domu i nawali wam na dywan.

Zdjęcie

<aside id="gallery">
  <p class="photonav">
    <a id="photonext" href="casey.html">Next ></a>
    <a id="photoprev" href="adagio.html">< Previous</a>
  </p>
  <figure id="photo">
    <img id="photoimg" src="gallery/1972-fer-500.jpg"
            alt="Fer" width="500" height="375">
    <figcaption>Fer, 1972</figcaption>
  </figure>
</aside>

Nic niezwykłego. Zdjęcie jest wstawione na stronę za pomocą elementu <img> w elemencie <figure>, łącza to zwykłe elementy <a>, a całość znajduje się w kontenerze <aside>. Ważne jednak, że łącza są prawdziwe i działają. Cały dalszy kod powinien znajdować się za skryptem wykrywającym. Jeśli użytkownik korzysta z przeglądarki nie obsługującej API historii, to kod związany z jego używaniem nie zostanie wykonany. Oczywiście zdarzają się też przeglądarki z wyłączoną obsługą skryptów.

Główna funkcja sterująca pobiera każdy z odnośników i przekazuje go do funkcji addClicker(), która tworzy procedurę obsługi zdarzenia click.

function setupHistoryClicks() {
  addClicker(document.getElementById("photonext"));
  addClicker(document.getElementById("photoprev"));
}

Oto kod źródłowy funkcji addClicker(). Pobiera element <a> i dodaje procedurę obsługi zdarzenia click. I właśnie w tej procedurze są najciekawsze rzeczy.

function addClicker(link) {
  link.addEventListener("click", function(e) {
    swapPhoto(link.href);
    history.pushState(null, null, link.href);
    e.preventDefault();
  }, false);
}

Ciekawe

Funkcja swapPhoto() wykonuje dwie pierwsze czynności naszej trzystopniowej iluzji. Pierwsza połowa jej kodu pobiera część adresu URL łącza nawigacyjnego — casey.html, adagio.html itd. — i tworzy adres URL do ukrytej strony zawierającej tylko kod HTML następnego zdjęcia.

function swapPhoto(href) {
  var req = new XMLHttpRequest();
  req.open("GET",
           "/wp-content/sources/html5rzeczowo/history/gallery/" +
             href.split("/").pop(),
           false);
  req.send(null);

Oto przykład kodu zwracanego pod adresem http://shebang.pl/wp-content/sources/html5rzeczowo/history/gallery/casey.html. (Możesz go obejrzeć wchodząc na tę stronę.)

<p class="photonav">
  <a id="photonext" href="brandy.html">Next ></a>
  <a id="photoprev" href="fer.html">< Previous</a>
</p>
<figure id="photo">
  <img id="photoimg" src="gallery/1984-casey-500.jpg"
          alt="Casey" width="500" height="375">
  <figcaption>Casey, 1984</figcaption>
</figure>

Wygląda znajomo? Powinno. To taki sam kod, jak użyty na stronie głównej do wyświetlenia pierwszego zdjęcia.

Druga część funkcji swapPhoto() wykonuje drugą część naszej trzyetapowej iluzji: wstawia pobrany kod na bieżącą stronę. Przypomnę, że całość wraz z podpisem rysunku znajduje się w kontenerze <aside>. Dzięki temu stawienie tego nowego kodu na stronę jest bardzo łatwe, wystarczy ustawić własność innerHTML elementu <aside> na własność responseText zwróconą przez XMLHttpRequest.

  if (req.status == 200) {
    document.getElementById("gallery").innerHTML = req.responseText;
    setupHistoryClicks();
    return true;
  }
  return false;
}

(Zwróć też uwagę na wywołanie funkcji setupHistoryClicks(). Jest konieczne, aby zresetować procedury obsługi zdarzenia click na nowo wstawionych łączach nawigacyjnych. Ustawienie innerHTML powoduje skasowanie starych łączy i ich procedur obsługi zdarzeń.)

Wracamy do funkcji addClicker(). Po zamienieniu zdjęcia pozostało nam już tylko jedno: ustawienie adresu URL w przeglądarce bez odświeżania strony.

Zamiana

history.pushState(null, null, link.href);

Funkcja history.pushState() przyjmuje trzy parametry:

  1. state może być dowolną strukturą danych w formacie JSON. Jest przekazywana do procedury obsługi zdarzeń popstate, o której więcej dowiesz się za moment. W tym przykładzie nie potrzebujemy informacji stanowych, więc parametr ten ustawiłem na null.
  2. title może być dowolnym łańcuchem. Tego parametry aktualnie najważniejsze przeglądarki nie używają. Jeśli chcesz ustawić tytuł strony zapisz go w argumencie state i ustaw ręcznie w procedurze zwrotnej popstate.
  3. url może być… adresem URL. Jest to ten adres, który pojawi się w pasku adresu przeglądarki.

Wywołanie history.pushState spowoduje natychmiastową zmianę adresu URL w pasku adresu przeglądarki. Czy to w takim razie koniec iluzji? Niezupełnie. Musisz jeszcze wiedzieć co się dzieje, gdy użytkownik naciśnie przycisk Wstecz.

Normalnie przejście na nową stronę (z pełnym odświeżeniem) powoduje wstawienie przez przeglądarkę do historii nowego adresu URL oraz pobranie i wyświetlenie nowej treści. Gdy użytkownik naciśnie przycisk Wstecz, przeglądarka usuwa jeden element z historii i wyświetla poprzednią stronę. Ale co się stanie teraz, gdy skróciliśmy tę nawigację, aby uniknąć odświeżania całej strony? Skoro sfałszowaliśmy przejście do przodu pod nowy adres URL, to możemy też sfałszować cofnięcie pod poprzedni adres. Kluczowe znaczenie w fałszowaniu cofania ma zdarzenie popstate.

Luksus

window.addEventListener("popstate", function(e) {
    swapPhoto(location.pathname);
});

Po wstawieniu do historii przeglądarki fałszywego adresu URL za pomocą metody history.pushState(), naciśnięcie przycisku Wstecz przez użytkownika powoduje zgłoszenie zdarzenia popstate na obiekcie window. To jest nasza szansa na dokończenie iluzji na dobre. Sprawienie, że coś zniknie to za mało. Trzeba jeszcze umieć to przywrócić.

W tym przykładzie przywrócenie czegoś z powrotem polega na wstawieniu z powrotem na stronę pierwszego zdjęcia. Zrobimy to przy użyciu metody swapPhoto() z bieżącą lokalizacją. Zanim zostanie wywołana funkcja zwrotna popstate, adres URL w pasku adresu przeglądarki zostanie zmieniony na poprzedni URL. Ponadto globalna własność location będzie już miała poprzedni adres URL.

Abyś lepiej to wszystko zrozumiał, poniżej przedstawiam opis w punktach całej iluzji:

  • Użytkownik ładuje stronę /wp-content/sources/html5rzeczowo/history/fer.html, na której widzi tekst i zdjęcie psa o imieniu Fer.
  • Użytkownik klika łącze „Next” będące elementem <a> z atrybutem href o wartości /wp-content/sources/html5rzeczowo/history/casey.html.
  • Zamiast pełnego przejścia na stronę /wp-content/sources/html5rzeczowo/history/casey.html, następuje przechwycenie kliknięcia przez procedurę obsługi zdarzenia click elementu <a> i wykonanie jej kodu.
  • Procedura obsługi zdarzenia click wywołuje funkcję swapPhoto(), która tworzy obiekt XMLHttpRequest w celu synchronicznego pobrania znajdującego się pod adresem /wp-content/sources/html5rzeczowo/history/gallery/casey.html kodu HTML.
  • Funkcja swapPhoto() ustawia własność innerHTML kontenera galerii zdjęć (elementu <aside>), zamieniając w ten sposób zdjęcie psa Fer na zdjęcie Caseya.
  • Na koniec procedura obsługi zdarzenia click wywołuje funkcję history.pushState(), aby zmienić adres URL w pasku adresu przeglądarki na /wp-content/sources/html5rzeczowo/history/casey.html.
  • Użytkownik klika przycisk Wstecz przeglądarki.
  • Przeglądarka „zauważa”, że do historii został dodany (przez funkcję history.pushState()) adres URL. Zamiast przejść pod poprzedni adres URL i na nowo narysować całą stronę, przeglądarka zmienia tylko zawartość paska adresu na poprzedni adres URL (/wp-content/sources/html5rzeczowo/history/fer.html) i zgłasza zdarzenie popstate.
  • Procedura obsługi zdarzeń popstate ponownie wywołuje funkcję swapPhoto(), tym razem jednak z poprzednim adresem URL, który już teraz jest widoczny w pasku adresu przeglądarki.
  • Ponownie przy użyciu obiektu XMLHttpRequest funkcja swapPhoto() pobiera kod HTML spod adresu /wp-content/sources/html5rzeczowo/history/gallery/fer.html i ustawia własność innerHTML kontenera <aside>, zamieniając zdjęcia Caseya na zdjęcie Fera.

Iluzja jest skończona. Wszystkie znaki (treść strony i adres URL w pasku adresu) wskazują, że użytkownik przeszedł na następną stronę, a potem wrócił do poprzedniej. Ale w tym czasie nie nastąpiło ani jedno pełne odświeżenie strony — wszystko było tylko skrupulatnie zaplanowaną iluzją.

Lektura uzupełniająca

Autor: Mark Pilgrim

Źródło: http://diveintohtml5.info/history.html

Tłumaczenie: Łukasz Piwko

Treść tej strony dostępna jest na zasadach licencji CC BY 3.0

Dyskusja

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *