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).

0voto

Victor Queiroz Points 113

Eh bien, je vois de nombreux exemples de remplacement de la pushState propriété de history mais je ne suis pas sûr que ce soit une bonne idée, je préférerais créer un service basé sur des événements avec une API similaire à celle de l'historique. De cette façon, vous pouvez contrôler non seulement l'état du push mais aussi celui du replace et cela ouvre des portes à de nombreuses autres implémentations qui ne dépendent pas de l'API globale de l'historique. Veuillez consulter l'exemple suivant :

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

Si vous avez besoin de la EventEmitter Le code ci-dessus est basé sur l'émetteur d'événements de NodeJS : https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js . utils.inherits peut être trouvée ici : https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970

0voto

jopfre Points 395

Je préfère ne pas écraser la méthode native de l'historique. Cette implémentation simple crée donc ma propre fonction appelée eventedPush state qui se contente de dispatcher un événement et de renvoyer history.pushState(). Les deux méthodes fonctionnent bien mais je trouve cette implémentation un peu plus propre car les méthodes natives continueront à fonctionner comme les futurs développeurs l'attendent.

function eventedPushState(state, title, url) {
    var pushChangeEvent = new CustomEvent("onpushstate", {
        detail: {
            state,
            title,
            url
        }
    });
    document.dispatchEvent(pushChangeEvent);
    return history.pushState(state, title, url);
}

document.addEventListener(
    "onpushstate",
    function(event) {
        console.log(event.detail);
    },
    false
);

eventedPushState({}, "", "new-slug");

0voto

Alberto Points 106

Sur la base de la solution donnée par @gblazex Si vous souhaitez suivre la même approche, mais en utilisant les fonctions de flèche, suivez l'exemple ci-dessous dans votre logique javascript :

private _currentPath:string;    
((history) => {
          //tracks "forward" navigation event
          var pushState = history.pushState;
          history.pushState =(state, key, path) => {
              this._notifyNewUrl(path);
              return pushState.apply(history,[state,key,path]); 
          };
        })(window.history);

//tracks "back" navigation event
window.addEventListener('popstate', (e)=> {
  this._onUrlChange();
});

Ensuite, implémentez une autre fonction _notifyUrl(url) qui déclenche toute action requise dont vous pourriez avoir besoin lorsque l'URL de la page courante est mise à jour (même si la page n'a pas été chargée du tout).

  private _notifyNewUrl (key:string = window.location.pathname): void {
    this._path=key;
    // trigger whatever you need to do on url changes
    console.debug(`current query: ${this._path}`);
  }

0voto

Andre Lopes Points 61

Comme je voulais juste la nouvelle URL, j'ai adapté les codes de @gblazex et @Alberto S. pour obtenir ceci :

(function(history){

  var pushState = history.pushState;
    history.pushState = function(state, key, path) {
    if (typeof history.onpushstate == "function") {
      history.onpushstate({state: state, path: path})
    }
    pushState.apply(history, arguments)
  }

  window.onpopstate = history.onpushstate = function(e) {
    console.log(e.path)
  }

})(window.history);

-1voto

Noitidart Points 3818

Je pense que ce sujet nécessite une solution plus moderne.

Je suis sûr que nsIWebProgressListener existait à l'époque, je suis surpris que personne ne l'ait mentionné.

A partir d'un framescript (pour la compatibilité avec e10s) :

let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

Puis en écoutant dans le onLoacationChange

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

Cela va apparemment attraper tous les pushState. Mais il y a un commentaire avertissant que cela "déclenche AUSSI pour le pushState". Donc, nous devons faire un peu plus de filtrage ici pour s'assurer qu'il s'agit seulement de choses pushState.

Basé sur : https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

Et : resource://gre/modules/WebNavigationContent.js

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