web-dev-qa-db-fra.com

Ajax avec history.pushState et popstate - que dois-je faire lorsque la propriété d'état popstate est nulle?

J'essaie l'API d'historique HTML5 avec chargement ajax de contenu.

J'ai un tas de pages de test reliées par des liens relatifs. J'ai ce JS, qui gère les clics sur ces liens. Lorsqu'un lien est cliqué, le gestionnaire saisit son attribut href et le transmet à ajaxLoadPage (), qui charge le contenu de la page demandée dans la zone de contenu de la page actuelle. (Mes PHP sont configurées pour renvoyer une page HTML complète si vous les demandez normalement, mais seulement un morceau de contenu si? Fragment = true est ajouté à l'URL de la demande.)

Ensuite, mon gestionnaire de clics appelle history.pushState () pour afficher l'URL dans la barre d'adresse et l'ajouter à l'historique du navigateur.

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

});

Lorsque vous ajoutez des URL à l'historique avec pushState, vous devez également inclure un gestionnaire d'événements pour l'événement popstate pour gérer les clics sur les boutons précédent ou suivant. (Si vous ne le faites pas, cliquer en arrière affiche l'URL que vous avez poussée vers l'historique dans la barre d'adresse, mais la page n'est pas mise à jour.) Mon gestionnaire de popstate récupère donc l'URL enregistrée dans la propriété d'état de chaque entrée que j'ai créée, et le transmet à ajaxLoadPage pour charger le contenu approprié.

Cela fonctionne bien pour les pages que mon gestionnaire de clics a ajoutées à l'historique. Mais que se passe-t-il avec les pages que le navigateur a ajoutées à l'historique lorsque je les ai demandées "normalement"? Disons que j'atterris normalement sur ma première page, puis que je navigue sur mon site avec des clics qui effectuent le chargement ajax - si j'essaie de revenir en arrière dans l'historique de cette première page, le dernier clic affiche l'URL de la première page, mais ne fait pas 'charge pas la page dans le navigateur. Pourquoi donc?

Je peux en quelque sorte voir que cela a quelque chose à voir avec la propriété state de ce dernier événement popstate. La propriété state est null pour cet événement, car ce ne sont que les entrées ajoutées à l'historique par pushState () ou replaceState () qui peuvent lui donner une valeur. Mais mon premier chargement de la page était une demande "normale" - comment se fait-il que le navigateur ne se contente pas de reculer et de charger l'URL initiale normalement?

22
And Finally

Je ne comprends toujours pas pourquoi le bouton Retour se comporte comme ceci - j'aurais pensé que le navigateur serait heureux de revenir à une entrée créée par une demande normale. Peut-être que lorsque vous insérez d'autres entrées avec pushState, l'historique cesse de se comporter normalement. Mais j'ai trouvé un moyen d'améliorer le fonctionnement de mon code. Vous ne pouvez pas toujours dépendre de la propriété d'état contenant l'URL vers laquelle vous souhaitez revenir. Mais revenir en arrière dans l'historique modifie l'URL dans la barre d'adresse comme vous vous en doutez, il peut donc être plus fiable de charger votre contenu en fonction de window.location. Suite à cela excellent exemple J'ai changé mon gestionnaire popstate afin qu'il charge le contenu en fonction de l'URL dans la barre d'adresse au lieu de rechercher une URL dans la propriété state.

Une chose à laquelle vous devez faire attention est que certains navigateurs (comme Chrome) déclenchent un événement popstate lorsque vous accédez initialement à une page. Lorsque cela se produit, vous êtes susceptible de recharger inutilement le contenu de votre page initiale. J'ai donc ajouté quelques morceaux de code de l'excellent pjax pour ignorer cette pop initiale.

$(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);

    });

});

Une amélioration que vous pourriez apporter à ce JS consiste uniquement à attacher le gestionnaire d'événements Click si le navigateur prend en charge l'API d'historique.

5
And Finally

Il s'agit d'une question plus ancienne, mais il existe une réponse beaucoup plus simple en utilisant le javascript natif pour ce problème.

Pour l'état initial, vous ne devez pas utiliser history.pushState mais plutôt history.replaceState.

Tous les arguments sont les mêmes pour les deux méthodes, à la seule différence que pushState crée un nouvel enregistrement d'historique et est donc la source de votre problème. replaceState ne remplace que l'état de cet enregistrement d'historique et se comportera comme prévu, c'est-à-dire revenir à la page de départ initiale.

14
Matt

J'ai rencontré le même problème que la question d'origine. Cette ligne

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

devrait être changé en

var initialPop = !popped;

C'est suffisant pour attraper la pop initiale. Ensuite, vous n'avez pas besoin d'ajouter la page d'origine au pushState. c'est-à-dire supprimer les éléments suivants:

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

Le code final basé sur AJAX tabs (et en utilisant 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');
       }
   });
}
6
Erin

En fait, je me suis retrouvé avec un besoin similaire aujourd'hui et j'ai trouvé le code que vous avez fourni très utile. Je suis arrivé au même problème que vous, et je crois que tout ce qui vous manque, c'est de pousser votre fichier d'index ou votre page d'accueil vers l'historique de la même manière que vous êtes toutes les pages suivantes.

Voici un exemple de ce que j'ai fait pour résoudre ce problème (je ne sais pas si c'est la bonne réponse, mais c'est simple et cela fonctionne!):

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

J'espère que cela t'aides!

1
mattblac

Je me rends compte que c'est une vieille question, mais en essayant de gérer facilement l'état comme celui-ci, il pourrait être préférable d'adopter l'approche suivante:

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

de cette façon, vous pouvez spécifier une fonction de rappel facultative sur l'objet d'état lors de l'ajout à l'historique, puis lorsque popstate est déclenchée, cette fonction sera appelée avec l'objet d'état comme paramètre.

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

Vous pouvez facilement l'étendre en fonction de vos besoins.

1
r3wt