2013-01-08 14 views
19

Próbuję API historii HTML5 z ładowaniem zawartości ajax.Ajax z history.pushState i popstate - co mam zrobić, gdy właściwość state popstate ma wartość NULL?

Mam kilka stron testowych połączonych względnymi linkami. Mam ten JS, który obsługuje kliknięcia tych łączy. Po kliknięciu łącza przewodnik pobiera atrybut href i przekazuje go do ajaxLoadPage(), która ładuje zawartość z żądanej strony do obszaru zawartości bieżącej strony. (Moje strony PHP są skonfigurowane tak, aby zwracać pełną stronę HTML, jeśli zwykle ich zażądasz, ale tylko fragment treści, jeśli? Fragment = true jest dołączany do adresu URL żądania.)

Następnie moja historia obsługi połączeń odpowiada historii .pushState(), aby wyświetlić adres URL w pasku adresu i dodać go do historii przeglądarki.

$(document).ready(function(){ 

    var content = $('#content'); 

    var ajaxLoadPage = function (url) { 
     console.log('Loading ' + url + ' fragment'); 
     content.load(url + '?fragment=true'); 
    } 

    // Handle click event of all links with href not starting with http, https or # 
    $('a').not('[href^=http], [href^=https], [href^=#]').on('click', function(e){ 

     e.preventDefault(); 
     var href = $(this).attr('href'); 
     ajaxLoadPage(href); 
     history.pushState({page:href}, null, href); 

    }); 

    // This mostly works - only problem is when popstate happens and state is null 
    // e.g. when we try to go back to the initial page we loaded normally 
    $(window).bind('popstate', function(event){ 
     console.log('Popstate'); 
     var state = event.originalEvent.state; 
     console.log(state); 
     if (state !== null) { 
      if (state.page !== undefined) { 
       ajaxLoadPage(state.page); 
      } 
     } 
    }); 

}); 

Podczas dodawania adresów URL do historii z pushState trzeba także zawierać obsługi zdarzeń dla popstate przypadku do czynienia z kliknięć na plecach lub przyciskami forward. (Jeśli tego nie zrobisz, kliknięcie z powrotem wyświetli adres URL przeniesiony do historii w pasku adresu, ale strona nie zostanie zaktualizowana.) Tak więc moja obsługa popstate przechwytuje adres URL zapisany we właściwości state każdego wpisu, który utworzyłem, i przekazuje go do ajaxLoadPage, aby załadować odpowiednią zawartość.

To działa poprawnie w przypadku stron, które moja aplikacja obsługi kliknięć została dodana do historii. Ale co się dzieje ze stronami przeglądarki dodanymi do historii, kiedy poprosiłem o "normalnie"? Powiedzmy, że normalnie ląduję na mojej pierwszej stronie, a potem poruszam się po mojej stronie za pomocą kliknięć, które ładują ten ajax - jeśli spróbuję wrócić do tej pierwszej strony, ostatnie kliknięcie pokazuje adres URL pierwszej strony, ale robi nie załaduje strony w przeglądarce. Dlaczego?

Mogę zobaczyć, że ma to coś wspólnego z własnością stanową tego ostatniego wydarzenia. Właściwość state ma wartość null dla tego zdarzenia, ponieważ to tylko wpisy dodawane do historii przez metodę pushState() lub replaceState(), która może nadać jej wartość. Ale moje pierwsze ładowanie strony było "normalnym" żądaniem - dlaczego przeglądarka nie cofa się i nie ładuje początkowego adresu URL?

Odpowiedz

4

Nadal nie rozumiem, dlaczego przycisk Wstecz działa tak - pomyślałem, że przeglądarka chętnie cofnie się do wpisu, który został utworzony przez normalne żądanie. Może po wstawieniu innych wpisów za pomocą funkcji pushState historia przestaje działać w normalny sposób. Ale znalazłem sposób, aby mój kod działał lepiej. Nie zawsze można polegać na właściwości stanu zawierającej adres URL, do którego chcesz wrócić. Jednak cofanie historii powoduje zmianę adresu URL na pasku adresu, tak jak można się spodziewać, więc ładowanie treści na podstawie window.location może być bardziej niezawodne. Po tym great example zmieniłem mój popstate handler, więc ładuje zawartość na podstawie adresu URL w pasku adresu, zamiast szukać adresu URL we właściwości state.

Jedną z rzeczy, na które należy zwrócić uwagę, jest to, że niektóre przeglądarki (takie jak Chrome) uruchamiają zdarzenie typu popstate po początkowym naciśnięciu strony. W takim przypadku możesz niepotrzebnie ponownie załadować zawartość początkowej strony. Dodałem więc fragmenty kodu ze znakomitego pjax, aby zignorować ten początkowy pop.

$(document).ready(function(){ 

    // Used to detect initial (useless) popstate. 
    // If history.state exists, pushState() has created the current entry so we can 
    // assume browser isn't going to fire initial popstate 
    var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href; 

    var content = $('#content'); 

    var ajaxLoadPage = function (url) { 

     console.log('Loading ' + url + ' fragment'); 
     content.load(url + '?fragment=true'); 

    } 

    // Handle click event of all links with href not starting with http, https or # 
    $('a').not('[href^=http], [href^=https], [href^=#]').on('click', function(e){ 

     e.preventDefault(); 
     var href = $(this).attr('href'); 
     ajaxLoadPage(href); 
     history.pushState({page:href}, null, href); 

    }); 

    $(window).bind('popstate', function(event){ 

     // Ignore inital popstate that some browsers fire on page load 
     var initialPop = !popped && location.href == initialURL; 
     popped = true; 
     if (initialPop) return; 

     console.log('Popstate'); 

     // By the time popstate has fired, location.pathname has been changed 
     ajaxLoadPage(location.pathname); 

    }); 

}); 

Jednym z ulepszeń, które można wprowadzić w tym JS, jest dołączenie obsługi zdarzeń kliknięcia, jeśli przeglądarka obsługuje interfejs API historii.

+0

Działa z jednym małym problemem, jeśli przeładujesz stronę, załaduje tylko część ajaxową strony. Chciałbym usłyszeć, jeśli ktoś ma rozwiązanie do przeładowania strony. – Hydrino

+0

@Hydrino Naciśnij stan z prawidłowym adresem URL i załaduj stronę z AJAX. –

1

Rzeczywiście znalazłem się z podobną potrzebą dzisiaj i okazało się, że kod, który podałeś, jest bardzo użyteczny. Przyszedłem do tego samego problemu co ty, i uważam, że wszystko, czego brakuje, przesuwa plik indeksu lub stronę główną do historii w taki sam sposób, jak wszystkie kolejne strony.

Oto przykład tego, co zrobiłem, aby rozwiązać ten problem (nie jestem pewien, czy jest to odpowiedź rację, ale to proste i to działa!):

var home = 'index.html'; 
history.pushState({page:home}, null, home); 

Nadzieja to pomaga!

+0

Dzięki mattblac, znalazłem inny sposób robienia tego, co może być bardziej elastyczne, opublikuję to tutaj. –

6

Wpadłem na ten sam problem co oryginalne pytanie. Ta linia

var initialPop = !popped && location.href == initialURL; 

należy zmienić

var initialPop = !popped; 

Jest to wystarczające, aby złapać początkowej pop. Następnie nie musisz dodawać oryginalnej strony do pushState. czyli usunąć następujące:

var home = 'index.html'; 
history.pushState({page:home}, null, home); 

ostateczny kod oparty na kartach AJAX (i przy użyciu Mootools):

if (this.supports_history_api()) { 
    var popped = ('state' in window.history && window.history.state !== null) 
    , changeTabBack = false; 

    window.addEvent('myShowTabEvent', function (url) { 
     if (url && !changingTabBack) 
      setLocation(url); 
     else 
      changingTabBack = false; 
     //Make sure you do not add to the pushState after clicking the back button 
    }); 

    window.addEventListener("popstate", function(e) { 
     var initialPop = !popped; 
     popped = true; 
     if (initialPop) 
      return; 

     var tabLink = $$('a[href="' + location.pathname + '"][data-toggle*=tab]')[0]; 
     if (tabLink) { 
      changingTabBack = true; 
      tabLink.tab('show'); 
     } 
    }); 
} 
+0

Świetnie! Dobra odpowiedź Erin. –

12

Jest to starsza pytanie, ale jest o wiele prostsze rozwiązanie przy użyciu natywnego JavaScript dla tej kwestii .

Dla stanu początkowego nie powinieneś używać history.pushState, lecz raczej history.replaceState.

Wszystkie argumenty są takie same dla obu metod, z tą różnicą, że pushState tworzy NOWY zapis historii, a tym samym jest źródłem problemu. replaceState zastępuje tylko stan rekordu historii i zachowuje się zgodnie z oczekiwaniami, czyli wraca do początkowej strony początkowej.

+1

To była tak kompletna odpowiedź na mój problem. Kiedy po raz pierwszy wejdziesz na stronę i użyjesz ajaxa do załadowania go, musisz zastąpić pierwszy stan, używając tego. Dzięki! – user1689571

0

Zdaję sobie sprawę, że jest to stare pytanie, ale kiedy próbuje łatwo tak zarządzać stan, może lepiej byłoby przyjąć następujące podejście:

$(window).on('popstate',function(e){ 
    var state = e.originalEvent.state; 
    if(state != null){ 
     if(state.hasOwnProperty('window')){ 
      //callback on window 
      window[state.window].call(window,state); 
     } 
    } 
}); 

W ten sposób można określić opcjonalnej funkcji zwrotnej na obiekcie stanu podczas dodawania do historii, a następnie, gdy wyzwala się popstate, funkcja ta zostanie wywołana z obiektem stanu jako parametrem.

function pushState(title,url,callback) 
{ 
    var state = { 
     Url : url, 
     Title : title, 
    }; 
    if(window[callback] && typeof window[callback] === 'function') 
    { 
     state.callback = callback; 
    } 
    history.pushState(state,state.Title,state.Url); 
} 

Można łatwo przedłużyć to w zależności od potrzeb.

-1

i wreszcie mówi:

bym myślał przeglądarka chętnie cofnąć się do wpisu, który został utworzony przez normalną życzenie.

Znalazłem wyjaśnienie zachowania tej dziwnej przeglądarki here. Wyjaśnienie jest

należy zapisać stan, gdy strona jest ładowana po raz pierwszy a następnie za każdym razem zmienia stan

testowałem to - to działa.

Oznacza to, że nie ma potrzeby wczytywania treści na podstawie okna window.location.

Mam nadzieję, że nie wprowadzę w błąd.

Powiązane problemy