web-dev-qa-db-fra.com

Interception de l'appel au bouton de retour dans mon AJAX

J'ai une application AJAX. Un utilisateur clique sur un bouton et l'affichage de la page change. dans leur navigateur.

Comment puis-je intercepter et réaffecter l'événement du bouton Retour? J'ai regardé des bibliothèques comme RSH (que je n'ai pas pu travailler ...), et j'ai entendu dire que l'utilisation de la balise de hachage aide en quelque sorte, mais je ne peux pas le comprendre.

72
Zack Burt

Ah, le bouton de retour. Vous pourriez imaginer que "back" déclenche un événement JavaScript que vous pouvez simplement annuler comme ceci:

document.onHistoryGo = function() { return false; }

Non. Il n'y a tout simplement pas un tel événement.

Si vous avez vraiment une application web (par opposition à un site Web avec quelques fonctionnalités ajaxy), il est raisonnable de reprendre le bouton de retour (avec des fragments sur l'URL, comme vous le mentionnez). Gmail fait cela. Je parle de quand votre application web en une seule page, tout autonome.

La technique est simple - chaque fois que l'utilisateur prend des mesures qui modifient les choses, redirigez vers la même URL que vous utilisez déjà, mais avec un fragment de hachage différent. Par exemple.

window.location.hash = "#deleted_something";
...
window.location.hash = "#did_something_else";

Si l'état général de votre application Web est lavable, c'est un excellent endroit pour utiliser un hachage. Supposons que vous ayez une liste d'e-mails, peut-être que vous concaténeriez tous leurs identifiants et statuts de lecture/non lus, et que vous preniez un hachage MD5, en l'utilisant comme identifiant de fragment.

Ce type de redirection (hachage uniquement) n'essaie pas de récupérer quoi que ce soit du serveur, mais il insère un emplacement dans la liste d'historique du navigateur. Ainsi, dans l'exemple ci-dessus, l'utilisateur clique sur "retour" et affiche maintenant # supprimé_quelque chose dans la barre d'adresse. Ils ont de nouveau riposté et sont toujours sur votre page, mais sans hachage. Revenez ensuite et ils en fait reviennent, d'où qu'ils viennent.

Maintenant, la partie la plus difficile, c'est que votre JavaScript détecte le moment où l'utilisateur a riposté (afin que vous puissiez revenir à l'état). Tout ce que vous faites est de regarder l'emplacement de la fenêtre et de voir quand elle change. Avec sondage. (Je sais, beurk, sondage. Eh bien, il n'y a rien de mieux pour le cross-browser en ce moment). Cependant, vous ne pourrez pas dire s'ils ont avancé ou reculé, vous devrez donc faire preuve de créativité avec vos identifiants de hachage. (Peut-être un hachage concaténé avec un numéro de séquence ...)

C'est l'essentiel du code. (C'est également ainsi que fonctionne le plugin jQuery History.)

var hash = window.location.hash;
setInterval(function(){
    if (window.location.hash != hash) {
        hash = window.location.hash;
        alert("User went back or forward to application state represented by " + hash);
    }
}, 100);
115
jpsimons

Pour donner une réponse à jour à une question ancienne (mais populaire):

HTML5 a introduit les méthodes history.pushState() et history.replaceState(), qui vous permettent respectivement d'ajouter et de modifier des entrées d'historique. Ces méthodes fonctionnent conjointement avec l'événement window.onpopstate.

L'utilisation de history.pushState() modifie le référent utilisé dans l'en-tête HTTP pour les objets XMLHttpRequest créés après avoir modifié l'état. Le référent sera l'URL du document dont la fenêtre est this au moment de la création de l'objet XMLHttpRequest.

Source: Manipulation de l'historique du navigateur à partir de Mozilla Developer Network.

49
gitaarik

En utilisant jQuery, j'ai fait une solution simple:

$(window).on('hashchange', function() {
    top.location = '#main';
    // Eventually alert the user describing what happened
});

Jusqu'à présent, seulement testé dans Google Chrome cependant.

Cela a résolu le problème de mon application Web qui est également fortement basée sur AJAX.

C'est peut-être un peu hack'ish - mais je l'appellerais un hacking élégant ;-) Chaque fois que vous essayez de naviguer en arrière, il ajoute une partie de hachage dans l'URI qui est techniquement ce qu'il essaie ensuite de naviguer en arrière.

Il intercepte à la fois le clic sur le bouton du navigateur et le bouton de la souris. Et vous ne pouvez pas le forcer en arrière en cliquant plusieurs fois par seconde, ce qui est un problème qui se produirait dans les solutions basées sur setTimeout ou setInterval.

10

J'apprécie vraiment l'explication donnée dans réponse de darkporter , mais je pense qu'elle peut être améliorée en utilisant un hachage " "événement . Comme l'explique darkporter, vous voulez vous assurer que tous vos boutons modifient la valeur window.location.hash.

  • Une façon consiste à utiliser <button> éléments, puis leur attacher des événements qui définissent ensuite window.location.hash = "#!somehashstring";.
  • Pour ce faire, vous pouvez également utiliser des liens pour vos boutons, comme <a href="#!somehashstring">Button 1</a>. Le hachage est automatiquement mis à jour lorsque vous cliquez sur ces liens.

La raison pour laquelle j'ai un point d'exclamation après le signe de hachage est de satisfaire le paradigme "hashbang" de Google ( en savoir plus ), ce qui est utile si vous souhaitez être indexé par les moteurs de recherche. Votre chaîne de hachage sera généralement constituée de paires nom/valeur comme #!color=blue&shape=triangle ou une liste comme #!/blue/triangle - tout ce qui a du sens pour votre application Web.

Ensuite, il vous suffit d'ajouter ce morceau de code qui se déclenchera chaque fois que la valeur de hachage change (y compris lorsque le bouton de retour est enfoncé ). Aucune boucle d'interrogation ne semble nécessaire.

window.addEventListener("hashchange", function(){
    console.log("Hash changed to", window.location.hash);
    // .... Do your thing here...
});

Je n'ai testé rien d'autre que Chrome 36, mais selon caniuse.com , cela devrait être disponible dans IE8 +, FF21 +, Chrome21 + et la plupart des autres navigateurs, sauf Opera Mini.

8
Luke

Il est très facile de désactiver le bouton BACK du navigateur , comme le code JQuery ci-dessous:

// History API 
if( window.history && window.history.pushState ){

  history.pushState( "nohb", null, "" );
  $(window).on( "popstate", function(event){
    if( !event.originalEvent.state ){
      history.pushState( "nohb", null, "" );
      return;
    }
  });
}

Vous pouvez le voir fonctionner et plus d'exemples ici dotnsf et ici thecssninja

Merci !

5

En raison de problèmes de confidentialité, il est impossible de désactiver le bouton de retour ou d'examiner l'historique de l'utilisateur, mais il est possible de créer de nouvelles entrées dans cet historique sans changer de page. Chaque fois que votre application AJAX change d'état, mettez à jour top.location avec un nouveau fragment URI .

top.location = "#new-application-state";

Cela créera une nouvelle entrée dans la pile d'historique du navigateur. Un certain nombre de bibliothèques AJAX gèrent déjà tous les détails ennuyeux, tels que Really Simple History .

4
Matthew

Dans ma situation, je voulais empêcher le bouton de retour d'envoyer l'utilisateur à la dernière page et à la place, prendre une action différente. En utilisant l'événement hashchange, j'ai trouvé une solution qui a fonctionné pour moi. Ce script suppose que la page sur laquelle vous l'utilisez n'utilise pas déjà de hachage, comme dans mon cas:

var hashChangedCount = -2;
$(window).on("hashchange", function () {
    hashChangedCount++;
    if (top.location.hash == "#main" && hashChangedCount > 0) {
        console.log("Ah ah ah! You didn't say the magic Word!");
    }
    top.location.hash = '#main';
});
top.location.hash = "#";

Le problème que j'ai rencontré avec certaines des autres réponses est que l'événement hashchange ne se déclencherait pas. Selon votre situation, cela peut également fonctionner pour vous.

2
Swisher Sweet

J'ai trouvé un moyen de remplacer le comportement normal de l'historique et de créer des événements de bouton back et forward distincts, en utilisant l'API d'historique HTML5 (cela ne fonctionnera pas dans IE = 9). C'est très hacky, mais efficace si vous vouliez intercepter les événements des boutons Précédent et Suivant et les gérer comme vous le souhaitez. Cela pourrait être utile dans un certain nombre de scénarios, par exemple si vous montriez une fenêtre de bureau à distance et que pour reproduire les clics des boutons avant et arrière sur la machine distante.

Les éléments suivants remplaceraient le comportement des boutons Précédent et Suivant:

var myHistoryOverride = new HistoryButtonOverride(function()
{
    console.log("Back Button Pressed");
    return true;
},
function()
{
    console.log("Forward Button Pressed");
    return true;
});

Si vous renvoyiez false dans l'une de ces fonctions de rappel, vous autoriseriez le navigateur à procéder à une opération normale de retour/retour et à quitter votre page.

Voici le script complet requis:

function HistoryButtonOverride(BackButtonPressed, ForwardButtonPressed)
{
    var Reset = function ()
    {
        if (history.state == null)
            return;
        if (history.state.customHistoryStage == 1)
            history.forward();
        else if (history.state.customHistoryStage == 3)
            history.back();
    }
    var BuildURLWithHash = function ()
    {
        // The URLs of our 3 history states must have hash strings in them so that back and forward events never cause a page reload.
        return location.Origin + location.pathname + location.search + (location.hash && location.hash.length > 1 ? location.hash : "#");
    }
    if (history.state == null)
    {
        // This is the first page load. Inject new history states to help identify back/forward button presses.
        var initialHistoryLength = history.length;
        history.replaceState({ customHistoryStage: 1, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.pushState({ customHistoryStage: 2, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.pushState({ customHistoryStage: 3, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.back();
    }
    else if (history.state.customHistoryStage == 1)
        history.forward();
    else if (history.state.customHistoryStage == 3)
        history.back();

    $(window).bind("popstate", function ()
    {
        // Called when history navigation occurs.
        if (history.state == null)
            return;
        if (history.state.customHistoryStage == 1)
        {
            if (typeof BackButtonPressed == "function" && BackButtonPressed())
            {
                Reset();
                return;
            }
            if (history.state.initialHistoryLength > 1)
                history.back(); // There is back-history to go to.
            else
                history.forward(); // No back-history to go to, so undo the back operation.
        }
        else if (history.state.customHistoryStage == 3)
        {
            if (typeof ForwardButtonPressed == "function" && ForwardButtonPressed())
            {
                Reset();
                return;
            }
            if (history.length > history.state.initialHistoryLength + 2)
                history.forward(); // There is forward-history to go to.
            else
                history.back(); // No forward-history to go to, so undo the forward operation.
        }
    });
};

Cela fonctionne par un concept simple. Lorsque notre page se charge, nous créons 3 états d'historique distincts (numérotés 1, 2 et 3) et naviguons le navigateur vers l'état numéro 2. Parce que l'état 2 est au milieu, l'événement de navigation d'historique suivant nous mettra dans l'état 1 ou 3, et à partir de cela, nous pouvons déterminer dans quelle direction l'utilisateur a appuyé. Et juste comme ça, nous avons intercepté un événement de bouton précédent ou précédent. Nous le traitons ensuite comme nous le voulons et revenons à l'état numéro 2 afin de pouvoir capturer le prochain événement de navigation dans l'historique.

Évidemment, vous devrez vous abstenir d'utiliser les méthodes history.replaceState et history.pushState lors de l'utilisation du script HistoryButtonOverride, sinon vous le briseriez.

2
Brian

Le bouton de retour dans les navigateurs essaie normalement de revenir à l'adresse précédente. il n'y a pas d'événement de bouton natif d'appareil enfoncé dans le navigateur javascript, mais vous pouvez le pirater en jouant avec l'emplacement et le hachage pour changer l'état de votre application.

imaginez l'application simple avec deux vues:

  • vue par défaut avec un bouton de récupération
  • affichage des résultats

pour l'implémenter, suivez cet exemple de code:

function goToView (viewName) {
  // before you want to change the view, you have to change window.location.hash.
  // it allows you to go back to the previous view with the back button.
  // so use this function to change your view instead of directly do the job.
  // page parameter is your key to understand what view must be load after this.

  window.location.hash = page
}

function loadView (viewName) {
    // change dom here based on viewName, for example:

    switch (viewName) {
      case 'index':
        document.getElementById('result').style.display = 'none'
        document.getElementById('index').style.display = 'block'
        break
      case 'result':
        document.getElementById('index').style.display = 'none'
        document.getElementById('result').style.display = 'block'
        break
      default:
        document.write('404')
    }
}

window.addEventListener('hashchange', (event) => {
  // oh, the hash is changed! it means we should do our job.
  const viewName = window.location.hash.replace('#', '') || 'index'

  // load that view
  loadView(viewName)
})


// load requested view at start.
if (!window.location.hash) {
  // go to default view if there is no request.
  goToView('index')
} else {
  // load requested view.
  loadView(window.location.hash.replace('#', ''))
}
1
Nainemom

Je fais quelque chose comme ça. Je garde un tableau avec les états d'application précédents.

Initié comme:

var backstack = [];

Ensuite, j'écoute les changements dans le hachage d'emplacement, et quand il change, je fais ceci:

if (backstack.length > 1 && backstack[backstack.length - 2] == newHash) {
    // Back button was probably pressed
    backstack.pop();
} else
    backstack.Push(newHash);

De cette façon, j'ai un moyen assez simple de garder une trace de l'historique des utilisateurs. Maintenant, si je veux implémenter un bouton de retour dans l'application (pas le navigateur-botton), je le fais juste faire:

window.location.hash = backstack.pop();
0
Trenskow