pushState creando entradas de historial duplicadas y sobrescribiendo las anteriores

15

He creado una aplicación web que utiliza los métodos history pushStatey replaceStatepara navegar por las páginas y actualizar el historial.

El guión en sí funciona casi a la perfección; cargará las páginas correctamente y arrojará los errores de la página cuando sea necesario lanzarlos. Sin embargo, he notado un extraño problema en el pushStateque empujará múltiples entradas duplicadas (y reemplazará las entradas anteriores) al historial.

Por ejemplo, digamos que hago lo siguiente (en orden):

  1. Cargue index.php (el historial será: Índice)

  2. Navegue a profile.php (el historial será: Perfil, Índice)

  3. Navegue a search.php (el historial será: Buscar, Buscar, Índice)

  4. Navega hasta dashboard.php

Entonces, finalmente, esto es lo que surgirá en mi historia (en orden de más reciente a más antiguo):

Tablero Tablero
Tablero
Tablero Índice de
búsqueda

El problema con esto es que cuando un usuario hace clic en los botones de avance o retroceso, será redirigido a la página incorrecta o tendrá que hacer clic varias veces para regresar una vez. Eso, y no tendrá sentido si van y revisan su historial.

Esto es lo que tengo hasta ahora:

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

Toda la ayuda es apreciada,
salud.

GROVER
fuente
Entonces, ¿solo desea propagar un cambio en el historial si el objetivo es diferente de la última entrada?
Aluan Haddad el
¿Está sucediendo esto aparentemente al azar? O son páginas específicas siempre las que causan entradas duplicadas en el historial.
SamVK
@AluanHaddad no Solo quiero propagar un cambio en el historial si hay un cambio en el historial (es decir, cuando un usuario está navegando por el sitio). Similar a lo que sucedería en una situación normal ... (pasar del índice al perfil para buscar en StackOverflow devolvería exactamente eso en mi historial)
GROVER.
@SamVK No importa en qué página, después de navegar desde la página de carga inicial (es decir, index.php), las entradas se duplicarán.
GROVER.
1
Bueno, no estoy muy seguro de eso, así que escribo en los comentarios. Veo que está agregando elementos del historial del navegador en su updateHistoryfunción. Ahora, es updateHistoryposible que se te llame dos veces, primero, cuando estás inicializando Traveler ( window.Traveller = new Traveller();, constructor-> init-> send-> render-> updateHistory), y luego también redirectdesde clickeventListener. No lo he probado, solo adivinanzas, así que lo agregué como un comentario y no como una respuesta.
Akshit Arora

Respuestas:

3

¿Tienes un controlador onpopstate ?

En caso afirmativo, compruebe allí también si no está presionando hacia el historial. Que algunas entradas se eliminen / reemplacen en la lista del historial podría ser una gran señal. De hecho, vea esta respuesta SO :

Tenga en cuenta que history.pushState () establece un nuevo estado como el último estado del historial. Y se llama a window.onpopstate cuando se navega (hacia atrás / adelante) entre los estados que ha establecido.

Por lo tanto, no presione PushState cuando se llame a window.onpopstate, ya que esto establecerá el nuevo estado como el último estado y luego no hay nada a lo que ir.

Una vez tuve exactamente el mismo problema que usted describe, pero en realidad fue causado por ir y venir para tratar de entender el error, que eventualmente desencadenaría el controlador popState. Desde ese controlador, entonces llamaría history.push. Así que al final, también tenía algunas entradas duplicadas y algunas faltantes, sin ninguna explicación lógica.

Eliminé la llamada a history.push, la reemplacé por history.replace después de verificar algunas condiciones, y después de que funcionó de maravilla :)

EDITAR -> CONSEJO

Si no puede localizar qué código está llamando a history.pushState:

Intente sobrescribiendo las funciones history.pushState y replaceState con el siguiente código:

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

Luego, cada vez que se disparen los puntos de interrupción, eche un vistazo a la pila de llamadas.

En la consola, si desea evitar un pushState, simplemente ingrese allowPush = false;o allowReplace = false;antes de reanudar. De esta manera, no te perderás ningún history.pushState, y puedes subir y encontrar el código que lo llama :)

Bonjour123
fuente
Lo hago, pero no empuja ni reemplaza el historial en absoluto: / solo muestra la página
GROVER.
Muy raro. Intente sobrescribir la función history.push con el siguiente código: const hP = history.pushState history.pushState = (loc) => {debugger; hP (loc)} y haga lo mismo para history.replaceState. Luego, cada vez que se disparen los puntos de interrupción, eche un vistazo a la pila de llamadas
Bonjour123,
Revisé la pila de llamadas y todo parece estar hecho correctamente, pero el historial no quiere funcionar. ¿Por qué debe Mozilla hacer que todo sea tan difícil :(
GROVER.
Gracias :) Y si pruebas Chrome, ¿qué resultados tienes?
Bonjour123