pushState créant des entrées d'historique en double et écrasant les précédentes

15

J'ai créé une application Web qui utilise les méthodes history pushStateet replaceStateafin de parcourir les pages tout en mettant à jour l'historique.

Le script lui-même fonctionne presque parfaitement; il chargera les pages correctement et générera des erreurs de page lorsqu'elles doivent être lancées. Cependant, j'ai remarqué un problème étrange où pushStatepoussera plusieurs entrées en double (et remplacera les entrées avant) dans l'historique.

Par exemple, disons que je fais ce qui suit (dans l'ordre):

  1. Charger index.php (l'historique sera: Index)

  2. Accédez à profile.php (l'historique sera: Profile, Index)

  3. Accédez à search.php (l'historique sera: Recherche, Recherche, Index)

  4. Accédez à dashboard.php

Enfin, voici ce qui va ressortir de mon histoire (par ordre du plus récent au plus ancien):

Dashboard
Dashboard
Dashboard
Search
Index

Le problème est que lorsqu'un utilisateur clique sur les boutons avant ou arrière, il sera soit redirigé vers la page incorrecte, soit devra cliquer plusieurs fois pour revenir en arrière une fois. Ça, et ça n'aura aucun sens s'ils vont vérifier leur histoire.

Voici ce que j'ai jusqu'à présent:

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);
        }
    }
});

Toute aide est appréciée,
Cheers.

GROVER.
la source
Vous ne souhaitez donc propager un changement d'historique que si la cible est différente de la dernière entrée?
Aluan Haddad
Est-ce que cela se produit au hasard? Ou des pages spécifiques sont-elles toujours celles qui provoquent des entrées d'historique en double.
SamVK
@AluanHaddad non Je souhaite uniquement propager un changement d'historique s'il y a un changement d'historique (c'est-à-dire lorsqu'un utilisateur navigue sur le site). Similaire à ce qui se passerait dans une situation normale ... (passer de l'index au profil pour rechercher sur StackOverflow retournerait exactement cela dans mon histoire)
GROVER.
@SamVK Peu importe la page, après avoir navigué à partir de la page de chargement initiale (c'est-à-dire index.php), les entrées se dupliqueront.
GROVER.
1
Eh bien, je n'en suis pas tout à fait sûr, alors écrivez dans les commentaires. Je vois que vous ajoutez des éléments d'historique du navigateur dans votre updateHistoryfonction. Maintenant, updateHistoryvous pouvez être appelé deux fois, d'abord, lorsque vous initialisez Traveler ( window.Traveller = new Traveller();, constructor-> init-> send-> render-> updateHistory), puis également à redirectpartir de clickeventListener. Je ne l'ai pas testé, juste des devinettes, alors ajoutez-le en tant que commentaire et non en tant que réponse.
Akshit Arora

Réponses:

3

Avez-vous un gestionnaire onpopstate ?

Si oui, vérifiez-y également si vous ne poussez pas vers l'histoire. Le fait que certaines entrées soient supprimées / remplacées dans la liste d'historique peut être un signe important. En effet, voyez cette réponse SO :

Gardez à l'esprit que history.pushState () définit un nouvel état comme le plus récent état d'historique. Et window.onpopstate est appelé lors de la navigation (en arrière / en avant) entre les états que vous avez définis.

Donc, ne poussez pasState lorsque le window.onpopstate est appelé, car cela définira le nouvel état comme dernier état et il n'y aura alors rien à avancer.

Une fois, j'ai eu exactement le même problème que vous décrivez, mais il a été causé en fait par des allers-retours pour essayer de comprendre le bogue, ce qui pourrait éventuellement déclencher le gestionnaire popState. À partir de ce gestionnaire, il appellera alors history.push. Donc, à la fin, j'avais également des entrées en double et d'autres manquantes, sans aucune explication logique.

J'ai supprimé l'appel à history.push, l'ai remplacé par history.replace après avoir vérifié certaines conditions, et après que cela a fonctionné comme un charme :)

MODIFIER -> CONSEIL

Si vous ne trouvez pas le code qui appelle history.pushState:

Essayez en remplaçant les fonctions history.pushState et replaceState par le code suivant:

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);
    }
}

Ensuite, chaque fois que les points d'arrêt sont déclenchés, consultez la pile des appels.

Dans la console, si vous souhaitez empêcher un pushState, entrez simplement allowPush = false;ou allowReplace = false;avant de reprendre. De cette façon, vous ne manquerez aucun history.pushState, et pourrez remonter et trouver le code qui l'appelle :)

Bonjour123
la source
Je le fais, mais il ne pousse ni ne remplace l'historique: / rend simplement la page
GROVER.
Vraiment étrange. Essayez d'écraser la fonction history.push avec le code suivant: const hP = history.pushState history.pushState = (loc) => {debugger; hP (loc)} et faites de même pour history.replaceState. Ensuite, chaque fois que les points d'arrêt sont déclenchés, consultez la pile des appels
Bonjour123
J'ai vérifié la pile d'appels et tout semble se faire correctement - mais l'historique ne veut pas fonctionner. Pourquoi Mozilla doit-il rendre tout si difficile :(
GROVER.
Merci :) Et si vous essayez sur Chrome, quels résultats avez-vous?
Bonjour123