176 votes

Comment être notifié des changements de l'historique via history.pushState ?

Donc maintenant que HTML5 introduit history.pushState pour modifier l'historique du navigateur, les sites web commencent à l'utiliser en combinaison avec Ajax au lieu de changer l'identifiant du fragment de l'URL.

Malheureusement, cela signifie que ces appels ne peuvent plus être détectés par onhashchange .

Ma question est la suivante : Existe-t-il un moyen fiable (hack ? ;)) de détecter quand un site web utilise history.pushState ? La spécification ne dit rien sur les événements qui sont soulevés (du moins, je n'ai rien trouvé).
J'ai essayé de créer une façade et j'ai remplacé window.history avec mon propre objet JavaScript, mais cela n'a pas eu d'effet du tout.

Plus d'explications : Je suis en train de développer un module complémentaire pour Firefox qui doit détecter ces changements et agir en conséquence.
Je sais qu'il y avait une question similaire il y a quelques jours qui demandait si écouter des Événements DOM serait efficace mais je préfère ne pas m'y fier car ces événements peuvent être générés pour de nombreuses raisons différentes.

Mise à jour :

Voici un jsfiddle (utilisez Firefox 4 ou Chrome 8) qui montre que onpopstate n'est pas déclenché lorsque pushState est appelé (ou est-ce que je fais quelque chose de mal ? N'hésitez pas à l'améliorer !).

Mise à jour 2 :

Un autre problème (secondaire) est que window.location n'est pas mis à jour lorsque l'on utilise pushState (mais je l'ai déjà lu ici sur SO je crois).

1voto

stef Points 6478

Vous pourriez vous lier à la window.onpopstate événement ?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

Dans la documentation :

Un gestionnaire d'événement pour l'événement popstate de la fenêtre.

Un événement popstate est envoyé à la fenêtre chaque fois que l'entrée active de l'historique active change. Si l'entrée d'historique en cours d'activation a été créée par un appel à history.pushState() ou a été affectée par un appel à history.replaceState(). par un appel à history.replaceState(), la propriété state de l'événement popstate contient une copie de l'objet state de l'entrée d'historique de l'entrée d'historique.

9 votes

J'ai déjà essayé et l'événement n'est déclenché que lorsque l'utilisateur revient en arrière dans l'historique (ou dans l'un des éléments de l'historique). history.go , .back ) sont appelées. Mais pas sur pushState . Voici ma tentative de test, peut-être que je fais quelque chose de mal : jsfiddle.net/fkling/vV9vd Il semble que c'est seulement de cette manière que si l'histoire a été changée par pushState l'objet d'état correspondant est transmis au gestionnaire d'événements lorsque les autres méthodes sont appelées.

2 votes

Ah. Dans ce cas, la seule chose à laquelle je pense est d'enregistrer un délai d'attente pour regarder la longueur de la pile de l'historique et déclencher un événement si la taille de la pile a changé.

1 votes

Ok, c'est une idée intéressante. Le seul problème est que le délai d'attente doit être déclenché assez souvent pour que l'utilisateur ne remarque pas de (long) retard (je dois charger et afficher les données pour la nouvelle URL). J'essaie toujours d'éviter les délais d'attente et les sondages dans la mesure du possible, mais jusqu'à présent, cela semble être la seule solution. J'attendrai encore d'autres propositions. Mais merci beaucoup pour l'instant !

1voto

nathancahill Points 2051

Puisque vous demandez un module complémentaire pour Firefox, voici le code que j'ai réussi à faire fonctionner. Utilisation de unsafeWindow es n'est plus recommandé et se trompe lorsque pushState est appelé depuis un script client après avoir été modifié :

Permission refusée pour accéder à la propriété history.pushState

Au lieu de cela, il y a une API appelée fonction d'exportation ce qui permet d'injecter la fonction dans window.history comme ça :

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});

1voto

Maxwell s.c Points 778

Je ne pense pas que ce soit une bonne idée de modifier les fonctions natives même si vous le pouvez, et vous devriez toujours garder le champ d'application, donc une bonne approche est de ne pas utiliser la fonction globale pushState, à la place, utilisez une de vos propres fonctions :

function historyEventHandler(state){ 
    // your stuff here
} 

window.onpopstate = history.onpushstate = historyEventHandler

function pushHistory(...args){
    history.pushState(...args)
    historyEventHandler(...args)
}

<button onclick="pushHistory(...)">Go to happy place</button>

Notez que si un autre code utilise la fonction native pushState, vous n'obtiendrez pas de déclenchement d'événement (mais si cela se produit, vous devez vérifier votre code).

1voto

Mir-Ismaili Points 1588

Remerciez @KalanjDjordjeDjordje para sa réponse . J'ai essayé de faire de son idée une solution complète :

const onChangeState = (state, title, url, isReplace) => { 
    // define your listener here ...
}

// set onChangeState() listener:
['pushState', 'replaceState'].forEach((changeState) => {
    // store original values under underscored keys (`window.history._pushState()` and `window.history._replaceState()`):
    window.history['_' + changeState] = window.history[changeState]

    window.history[changeState] = new Proxy(window.history[changeState], {
        apply (target, thisArg, argList) {
            const [state, title, url] = argList
            onChangeState(state, title, url, changeState === 'replaceState')

            return target.apply(thisArg, argList)
        },
    })
})

0voto

Flimm Points 8870

La réponse de galambalazs pattes de singe window.history.pushState y window.history.replaceState mais pour une raison quelconque, il a cessé de fonctionner pour moi. Voici une alternative qui n'est pas aussi agréable car elle utilise l'interrogation :

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X