pushState tworzy zduplikowane wpisy historii i zastępuje poprzednie

15

Stworzyłem aplikację internetową, która korzysta z metod history pushStatei replaceState, aby poruszać się po stronach, jednocześnie aktualizując historię.

Sam skrypt działa prawie idealnie; ładuje strony poprawnie i wyrzuca błędy strony, gdy trzeba je wyrzucić. Zauważyłem jednak dziwny problem polegający na pushStatewypychaniu wielu zduplikowanych wpisów (i zastępowaniu wcześniejszych) do historii.

Załóżmy na przykład, że wykonuję następujące czynności (w kolejności):

  1. Załaduj index.php (historia będzie: Indeks)

  2. Przejdź do profile.php (historia będzie: Profil, Indeks)

  3. Przejdź do search.php (historia będzie: Szukaj, Szukaj, Indeks)

  4. Przejdź do dashboard.php

I wreszcie oto, co pojawi się w mojej historii (w kolejności od najnowszych do najstarszych):

Pulpit nawigacyjny
Pulpit
nawigacyjny Indeks
wyszukiwania

Problem polega na tym, że gdy użytkownik kliknie przyciski przewijania do przodu lub do tyłu, albo zostanie przekierowany na niewłaściwą stronę, albo będzie musiał kliknąć kilka razy, aby wrócić raz. To i nie będzie sensu, jeśli pójdą i sprawdzą swoją historię.

Oto co mam do tej pory:

var Traveller = function(){
    this._initialised = false;

    this._pageData = null;
    this._pageRequest = null;

    this._history = [];
    this._currentPath = null;
    this.abort = function(){
        if(this._pageRequest){
            this._pageRequest.abort();
        }
    };
    // initialise traveller (call replaceState on load instead of pushState)
    return this.init();
};

/*1*/Traveller.prototype.init = function(){
    // get full pathname and request the relevant page to load up
    this._initialLoadPath = (window.location.pathname + window.location.search);
    this.send(this._initialLoadPath);
};
/*2*/Traveller.prototype.send = function(path){
    this._currentPath = path.replace(/^\/+|\/+$/g, "");

    // abort any running requests to prevent multiple
    // pages from being loaded into the DOM
    this.abort();

    return this._pageRequest = _ajax({
        url: path,
        dataType: "json",
        success: function(response){
            // render the page to the dom using the json data returned
            // (this part has been skipped in the render method as it
            // doesn't involve manipulating the history object at all
            window.Traveller.render(response);
        }
    });
};
/*3*/Traveller.prototype.render = function(data){
    this._pageData = data;
    this.updateHistory();
};
/*4*/Traveller.prototype.updateHistory = function(){
    /* example _pageData would be:
    {
        "page": {
            "title": "This is a title",
            "styles": [ "stylea.css", "styleb.css" ],
            "scripts": [ "scripta.js", "scriptb.js" ]
        }
    }
    */
    var state = this._pageData;
    if(!this._initialised){
        window.history.replaceState(state, state.title, "/" + this._currentPath);
        this._initialised = true;
    } else {
        window.history.pushState(state, state.title, "/" + this._currentPath);  
    }
    document.title = state.title;
};

Traveller.prototype.redirect = function(href){
    this.send(href);
};

// initialise traveller
window.Traveller = new Traveller();

document.addEventListener("click", function(event){
    if(event.target.tagName === "a"){
        var link = event.target;
        if(link.target !== "_blank" && link.href !== "#"){
            event.preventDefault();
            // example link would be /profile.php
            window.Traveller.redirect(link.href);
        }
    }
});

Cała pomoc jest doceniana, na
zdrowie.

GROVER.
źródło
Czy chcesz więc propagować zmianę historii tylko wtedy, gdy cel jest inny niż ostatni wpis?
Aluan Haddad
Czy dzieje się to na pozór losowo? Lub to konkretne strony zawsze powodują powielanie wpisów historii.
SamVK
@AluanHaddad nie Chcę propagować zmianę historii tylko wtedy, gdy nastąpi zmiana historii (tj. Gdy użytkownik porusza się po stronie). Podobnie jak w normalnej sytuacji ... (przejście z indeksu do profilu do wyszukiwania na StackOverflow zwróci dokładnie to, co w mojej historii)
GROVER.
@SamVK Nie ma znaczenia, po której stronie, po przejściu od początkowej strony ładowania (tj. Index.php), wpisy się powielą.
GROVER.
1
Nie jestem do końca pewien, więc piszę w komentarzach. Widzę, że dodajesz rzeczy z historii przeglądarki do swojej updateHistoryfunkcji. Teraz updateHistorymoże być wywoływany dwa razy, po pierwsze, podczas inicjowania produktu Traveler ( window.Traveller = new Traveller();, constructor-> init-> send-> render-> updateHistory), a następnie również redirectz poziomu clickeventListener. Nie testowałem tego, tylko zgadywanie, więc dodając go jako komentarz, a nie odpowiedź.
Akshit Arora,

Odpowiedzi:

3

Czy masz program obsługi onpopstate ?

Jeśli tak, sprawdź również, czy nie przesuwasz się do historii. To, że niektóre wpisy są usuwane / zastępowane na liście historii, może być dużym znakiem. Rzeczywiście, zobacz tę SO odpowiedź :

Należy pamiętać, że history.pushState () ustawia nowy stan jako najnowszy stan historii. I window.onpopstate jest wywoływany podczas nawigacji (wstecz / do przodu) między ustawionymi stanami.

Nie należy więc pushState, gdy wywoływane jest okno.onpopstate, ponieważ ustawi to nowy stan jako ostatni stan, a następnie nie będzie już nic, do czego można przejść.

Kiedyś miałem dokładnie ten sam problem, co opisałeś, ale tak naprawdę spowodowałem to, że próbowałem zrozumieć błąd, który ostatecznie uruchomiłby program obsługi popState. Z tego modułu obsługi wywołuje następnie history.push. Na koniec miałem też kilka zduplikowanych wpisów, a niektórych brakowało, bez żadnego logicznego wyjaśnienia.

Usunąłem wywołanie history.push, zastąpiłem go history.replace po sprawdzeniu niektórych warunków, a potem zadziałało jak urok :)

EDYCJA -> WSKAZÓWKA

Jeśli nie możesz zlokalizować, który kod wywołuje history.pushState:

Spróbuj, zastępując funkcje history.pushState i replaceState następującym kodem:

window.pushStateOriginal = window.history.pushState.bind(window.history);
window.history.pushState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowPush  = true;
    debugger;
    if (allowPush ) {
        window.pushStateOriginal(...args);
    }
}
//the same for replaceState
window.replaceStateOriginal = window.history.replaceState.bind(window.history);
window.history.replaceState = function () {
    var args = Array.prototype.slice.call(arguments, 0);
    let allowReplace  = true;
    debugger;
    if (allowReplace) {
        window.replaceStateOriginal(...args);
    }
}

Następnie za każdym razem, gdy punkty przerwania są wyzwalane, spójrz na stos wywołań.

Jeśli chcesz uniemożliwić pushState w konsoli, po prostu wpisz allowPush = false;lub allowReplace = false;przed wznowieniem. W ten sposób nie przegapisz żadnej history.pushState i możesz przejść w górę i znaleźć kod, który to wywołuje :)

Bonjour123
źródło
Robię, ale wcale nie przesuwa ani nie zastępuje historii: / po prostu renderuje stronę
GROVER.
Bardzo dziwne. Spróbuj zastąpić funkcję history.push następującym kodem: const hP = history.pushState history.pushState = (loc) => {debugger; hP (loc)} i zrób to samo dla history.replaceState. Następnie za każdym razem, gdy punkty przerwania zostaną uruchomione, spójrz na stos wywołań
Bonjour123
Sprawdziłem stos wywołań i wszystko wydaje się być wykonane poprawnie - ale historia nie chce działać. Dlaczego Mozilla musi wszystko utrudniać :(
GROVER.
Dzięki :) A jeśli spróbujesz w Chrome, jakie masz wyniki?
Bonjour123,