Abfangen des Aufrufs der Zurück-Schaltfläche in meiner AJAX-Anwendung

76

Ich habe eine AJAX-App. Ein Benutzer klickt auf eine Schaltfläche und die Anzeige der Seite ändert sich. Sie klicken auf die Schaltfläche "Zurück" und erwarten, dass sie in den ursprünglichen Zustand zurückkehren. Stattdessen wechseln sie in ihrem Browser zur vorherigen Seite.

Wie kann ich das Ereignis "Zurück" abfangen und neu zuweisen? Ich habe mir Bibliotheken wie RSH angesehen (die ich nicht zur Arbeit bringen konnte ...), und ich habe gehört, dass die Verwendung des Hash-Tags irgendwie hilft, aber ich kann keinen Sinn daraus machen.

Zack Burt
quelle

Antworten:

119

Ah, der Zurück-Knopf. Sie können sich vorstellen, dass "zurück" ein JavaScript-Ereignis auslöst, das Sie einfach wie folgt abbrechen können:

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

Nein so. Es gibt einfach kein solches Ereignis.

Wenn Sie wirklich eine Web-App haben (im Gegensatz zu nur einer Website mit einigen Ajaxy-Funktionen), ist es sinnvoll, die Schaltfläche "Zurück" zu übernehmen (mit Fragmenten in der URL, wie Sie bereits erwähnt haben). Google Mail macht das. Ich spreche davon, wenn Ihre Web-App auf einer Seite in sich geschlossen ist.

Die Technik ist einfach: Wenn der Benutzer Maßnahmen ergreift, mit denen Änderungen vorgenommen werden, leiten Sie zu derselben URL weiter, auf der Sie sich bereits befinden, jedoch mit einem anderen Hash-Fragment. Z.B

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

Wenn der Gesamtstatus Ihrer Web-App hashbar ist, ist dies ein großartiger Ort, um einen Hash zu verwenden. Angenommen, Sie haben eine Liste mit E-Mails, verketten möglicherweise alle ihre IDs und lesen / ungelesenen Status und verwenden einen MD5-Hash, der als Fragmentkennung verwendet wird.

Diese Art der Umleitung (nur Hash) versucht nicht, etwas vom Server abzurufen, fügt jedoch einen Slot in die Verlaufsliste des Browsers ein. Im obigen Beispiel drückt der Benutzer auf "Zurück" und zeigt nun #deleted_something in der Adressleiste an. Sie schlagen wieder zurück und sind immer noch auf Ihrer Seite, aber ohne Hash. Dann wieder zurück und sie gehen tatsächlich zurück, wohin sie auch kommen.

Jetzt ist es jedoch schwierig, Ihr JavaScript erkennen zu lassen, wenn der Benutzer zurückschlägt (damit Sie den Status wiederherstellen können). Sie müssen lediglich die Position des Fensters beobachten und sehen, wann es sich ändert. Mit Abfrage. (Ich weiß, igitt, Umfrage. Nun, es gibt momentan nichts Besseres für einen Cross-Browser). Sie können jedoch nicht erkennen, ob sie vorwärts oder rückwärts gegangen sind, daher müssen Sie mit Ihren Hash-IDs kreativ werden. (Vielleicht ein mit einer Sequenznummer verketteter Hash ...)

Dies ist der Kern des Codes. (So ​​funktioniert auch das jQuery History-Plugin.)

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);
jpsimons
quelle
57

So geben Sie eine aktuelle Antwort auf eine alte (aber beliebte) Frage:

In HTML5 wurden die Methoden history.pushState()und eingeführt history.replaceState(), mit denen Sie Verlaufseinträge hinzufügen bzw. ändern können. Diese Methoden arbeiten in Verbindung mit dem window.onpopstateEreignis.

Verwenden von history.pushState()Änderungen Der Referrer, der im HTTP-Header für XMLHttpRequestObjekte verwendet wird, die nach dem Ändern des Status erstellt wurden. Der Verweis ist die URL des Dokuments, dessen Fenster sich thiszum Zeitpunkt der Erstellung des XMLHttpRequestObjekts befindet.

Quelle: Bearbeiten des Browserverlaufs im Mozilla Developer Network.

Gitaarik
quelle
10

Mit jQuery habe ich eine einfache Lösung gefunden:

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

Bisher nur in Google Chrome getestet.

Dies löste das Problem für meine Web-App, die ebenfalls stark AJAX-basiert ist.

Es ist vielleicht ein bisschen hack'ish - aber ich würde es elegantes Hacken nennen ;-) Immer wenn Sie versuchen, rückwärts zu navigieren, wird ein Hash-Teil in der URI eingefügt, über den technisch gesehen rückwärts navigiert wird.

Es fängt sowohl das Klicken auf die Browser- als auch die Maustaste ab. Und Sie können es auch nicht rückwärts brute erzwingen, indem Sie mehrmals pro Sekunde klicken. Dies ist ein Problem, das bei Lösungen auftreten würde, die auf setTimeout oder setInterval basieren.

Jens
quelle
Ich gehe davon aus, dass das Klicken mit der rechten Maustaste auf die Schaltfläche "Zurück" und das gleichzeitige Zurückgehen von zwei Schritten trotzdem funktionieren würde.
Hjulle
9

Ich schätze die Erklärung in der Antwort von darkporter sehr , aber ich denke, dass sie durch die Verwendung eines " Hashchange " -Ereignisses verbessert werden kann . Wie darkporter erklärt hat, möchten Sie sicherstellen, dass alle Ihre Schaltflächen den Wert von window.location.hash ändern.

  • Eine Möglichkeit besteht darin, <button>Elemente zu verwenden und ihnen dann Ereignisse zuzuweisen, die dann festgelegt werden window.location.hash = "#!somehashstring";.
  • Eine andere Möglichkeit, dies zu tun, besteht darin, nur Links für Ihre Schaltflächen zu verwenden, z <a href="#!somehashstring">Button 1</a>. Der Hash wird automatisch aktualisiert, wenn auf diese Links geklickt wird.

Der Grund, warum ich nach dem Hash-Zeichen ein Ausrufezeichen habe, besteht darin, das "Hashbang" -Paradigma von Google zu erfüllen ( lesen Sie mehr darüber ). Dies ist nützlich, wenn Sie von Suchmaschinen indiziert werden möchten. Ihre Hash-Zeichenfolge besteht normalerweise aus Name / Wert-Paaren #!color=blue&shape=triangleoder einer Liste wie #!/blue/triangle- was auch immer für Ihre Web-App sinnvoll ist.

Dann müssen Sie nur noch diesen Code hinzufügen, der ausgelöst wird, wenn sich der Hash-Wert ändert (auch wenn die Zurück-Taste gedrückt wird ). Es scheint keine Abfrageschleife erforderlich zu sein.

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

Ich habe nur in Chrome 36 getestet, aber laut caniuse.com sollte dies in IE8 +, FF21 +, Chrome21 + und den meisten anderen Browsern außer Opera Mini verfügbar sein.

Luke
quelle
1
Nur ein Update - Google empfiehlt den "Hashbang" -Ansatz nicht mehr, da sie jetzt erfolgreich
crawlen
7

Es ist sehr einfach, die Schaltfläche ZURÜCK des Browsers zu deaktivieren , wie im folgenden JQuery-Code:

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

Sie können sehen, dass es funktioniert und weitere Beispiele hier dotnsf und hier thecssninja

Vielen Dank !

Roberval Sena 山 本
quelle
1
Dies war das einzige Snippet auf dieser Seite, das in Chrome v52.0 für mich funktioniert hat. Vielen Dank.
Ralpharama
Dies funktioniert. Aber der Benutzer kann zur vorherigen Seite gehen, indem er mit der rechten Maustaste auf die Schaltfläche "Zurück" klickt.
Vimal
@VimalS YESSS! das ist es! du musst konfigurieren! Bitte lesen Sie die zugehörigen Seiten, die ich gesendet habe! Es gibt noch viel mehr in der Verlaufs-API ... pluss: Der normale Benutzer kennt den "richtigen Klick"? .. nur einen Sturz, den ich denke ... BITTE. Passen Sie dies an Ihre Bedürfnisse an!
Roberval Sena
5

Ich habe einen Weg gefunden, um das normale Verlaufsverhalten zu überschreiben und mithilfe der HTML5-Verlaufs-API eindeutige backund forwardSchaltflächenereignisse zu erstellen (dies funktioniert in IE 9 nicht). Dies ist sehr hackig, aber effektiv, wenn Sie die Ereignisse der Schaltflächen "Zurück" und "Vorwärts" abfangen und behandeln möchten, wie Sie möchten. Dies kann in einer Reihe von Szenarien hilfreich sein, z. B. wenn Sie ein Remotedesktopfenster anzeigen und die Schaltflächenklicks auf dem Remotecomputer reproduzieren müssen.

Folgendes würde das Verhalten der Schaltflächen "Zurück" und "Vorwärts" überschreiben:

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

Wenn Sie in einer dieser Rückruffunktionen false zurückgegeben haben, können Sie dem Browser erlauben, mit einem normalen Vorwärts- / Rückwärtsvorgang fortzufahren und Ihre Seite zu verlassen.

Hier ist das vollständige Skript erforderlich:

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

Dies funktioniert nach einem einfachen Konzept. Wenn unsere Seite geladen wird, erstellen wir 3 verschiedene Verlaufszustände (nummeriert 1, 2 und 3) und navigieren den Browser zu Zustand Nr. 2. Da sich Zustand 2 in der Mitte befindet, versetzt uns das nächste Verlaufsnavigationsereignis entweder in den Zustand 1 oder 3, und daraus können wir bestimmen, in welche Richtung der Benutzer gedrückt hat. Und einfach so haben wir ein Zurück- oder Vorwärts-Schaltflächenereignis abgefangen. Wir behandeln es dann, wie wir wollen, und kehren zu Status 2 zurück, damit wir das nächste Navigationsereignis für den Verlauf erfassen können.

Natürlich müssten Sie bei der Verwendung des HistoryButtonOverride-Skripts auf die Verwendung der Methoden history.replaceState und history.pushState verzichten, da Sie sie sonst beschädigen würden.

Brian
quelle
4

Aus Datenschutzgründen ist es unmöglich, die Zurück-Schaltfläche zu deaktivieren oder den Benutzerverlauf zu überprüfen. Es ist jedoch möglich, neue Einträge in diesem Verlauf zu erstellen, ohne die Seiten zu ändern. Wenn Ihre AJAX-Anwendung den Status ändert, aktualisieren Sie sie top.locationmit einem neuen URI-Fragment .

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

Dadurch wird ein neuer Eintrag im Verlaufsstapel des Browsers erstellt. Einige AJAX-Bibliotheken verarbeiten bereits alle langweiligen Details, z. B. Really Simple History .

Matthew
quelle
Welche anderen langweiligen Details muss ich neben der Einstellung von top.location beachten?
Zack Burt
Da das "Onload" -Ereignis nicht ausgelöst wird, wenn der Benutzer zurückklickt, benötigen Sie etwas, um ein Ereignis auszulösen. Es gibt auch einige Unterschiede zwischen Browsern. Eine gute AJAX-Bibliothek würde all diese Details zusammenfassen, einschließlich des setTimeout()Beispiels in darkporter.
Matthew
2

Ich mache so etwas. Ich behalte ein Array mit früheren App-Status.

Initiiert als:

var backstack = [];

Dann höre ich auf Änderungen im Location-Hash und wenn es sich ändert, mache ich Folgendes:

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

Auf diese Weise habe ich eine etwas einfache Möglichkeit, den Benutzerverlauf zu verfolgen. Wenn ich nun einen Zurück-Button in der App implementieren möchte (nicht den Browser-Botton), lasse ich es einfach tun:

window.location.hash = backstack.pop();
Trenskow
quelle
2
Klingt so, als würden Sie die Funktionalität der Zurück-Taste reproduzieren. Das ist interessant, aber wahrscheinlich nicht die Antwort auf die Frage des OP.
Luke
2

In meiner Situation wollte ich verhindern, dass die Schaltfläche "Zurück" den Benutzer zur letzten Seite sendet, und stattdessen eine andere Aktion ausführen. Mit der hashchangeVeranstaltung habe ich eine Lösung gefunden, die für mich funktioniert hat. Dieses Skript setzt voraus, dass die Seite, auf der Sie es verwenden, noch keinen Hash verwendet, wie in meinem Fall:

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 = "#";

Das Problem, das ich mit einigen der anderen Antworten hatte, ist, dass das hashchangeEreignis nicht ausgelöst werden würde. Abhängig von Ihrer Situation kann dies auch für Sie funktionieren.

Swisher Sweet
quelle
1

Die Zurück-Schaltfläche in den Browsern versucht normalerweise, zur vorherigen Adresse zurückzukehren. In Browser-Javascript gibt es kein Ereignis, bei dem die Zurück-Taste des nativen Geräts gedrückt wurde. Sie können es jedoch hacken, indem Sie mit Standort und Hash spielen, um Ihren App-Status zu ändern.

Stellen Sie sich die einfache App mit zwei Ansichten vor:

  • Standardansicht mit einer Abrufschaltfläche
  • Ergebnisansicht

Folgen Sie zur Implementierung diesem Beispielcode:

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('#', ''))
}
Nainemom
quelle