web-dev-qa-db-ja.com

history.pushStateとpopstateを使用したAjax-popstate stateプロパティがnullの場合はどうすればよいですか?

コンテンツのajaxロードでHTML5履歴APIを試しています。

相対リンクで接続されたテストページがたくさんあります。これらのリンクのクリックを処理するこのJSがあります。リンクがクリックされると、ハンドラーはhref属性を取得し、それをajaxLoadPage()に渡します。これは、要求されたページから現在のページのコンテンツ領域にコンテンツをロードします。 (My PHPページは通常のページをリクエストすると完全なHTMLページを返すように設定されますが、リクエストのURLに?fragment = trueが追加されるとコンテンツのチャンクのみが返されます。)

次に、クリックハンドラーはhistory.pushState()を呼び出して、アドレスバーにURLを表示し、ブラウザーの履歴に追加します。

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

});

PushStateを使用してURLを履歴に追加するときは、戻るまたは進むボタンのクリックを処理するために、popstateイベントのイベントハンドラーも含める必要があります。 (これを行わない場合、戻るをクリックすると、アドレスバーに履歴にプッシュしたURLが表示されますが、ページは更新されません。)したがって、popstateハンドラーは、作成した各エントリのstateプロパティに保存されたURLを取得し、適切なコンテンツをロードするためにajaxLoadPageに渡します。

これは、クリックハンドラーが履歴に追加したページでは正常に機能します。しかし、「通常」ページをリクエストしたときにブラウザが履歴に追加したページはどうなりますか?通常、最初のページに着地し、そのajax読み込みを行うクリックでサイトをナビゲートするとします-その後、履歴をその最初のページに戻そうとすると、最後のクリックで最初のページのURLが表示されますが、ブラウザにページをロードしないでください。何故ですか?

これは、最後のpopstateイベントのstateプロパティと関係があることがわかります。 stateイベントは、pushState()またはreplaceState()によって値を与えることができるエントリのみが履歴に追加されるため、そのイベントではnullです。しかし、ページの最初の読み込みは「通常の」リクエストでした。ブラウザが最初のURLを通常どおりに戻って読み込まないのはなぜですか?

20
And Finally

戻るボタンがこのように動作する理由はまだわかりません。通常のリクエストで作成されたエントリにブラウザが戻ると喜んでいると思います。 pushStateを使用して他のエントリを挿入すると、履歴が通常の方法で動作しなくなる可能性があります。しかし、コードをより良く機能させる方法を見つけました。戻りたいURLを含む状態プロパティに常に依存できるとは限りません。ただし、履歴をステップバックすると、予想どおりアドレスバーのURLが変更されるため、window.locationに基づいてコンテンツを読み込む方が信頼性が高い場合があります。これに続いて 素晴らしい例 popstateハンドラーを変更し、stateプロパティでURLを探す代わりに、アドレスバーのURLに基​​づいてコンテンツを読み込むようにしました。

注意しなければならないことの1つは、一部のブラウザー(Chromeなど)が最初にページにヒットしたときにpopstateイベントを起動することです。これが発生すると、初期ページのコンテンツを不必要にリロードすることになります。そのため、優れた pjax からいくつかのコードを追加して、その初期ポップを無視しました。

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

    });

});

このJSの改善点の1つは、ブラウザーが履歴APIをサポートしている場合にのみクリックイベントハンドラーをアタッチすることです。

5
And Finally

これは古い質問ですが、この問題に対してネイティブjavascriptを使用したはるかに簡単な回答があります。

初期状態では、history.pushStateではなくhistory.replaceStateを使用する必要があります。

すべての引数は両方のメソッドで同じですが、唯一の違いはpushStateが新しい履歴レコードを作成するため、問題の原因になることです。 replaceStateは、その履歴レコードの状態のみを置き換え、期待どおりに動作します。つまり、最初の開始ページに戻ります。

13
Matt

元の質問と同じ問題に遭遇しました。この行

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

に変更する必要があります

var initialPop = !popped;

最初のポップをキャッチするにはこれで十分です。その後、元のページをpushStateに追加する必要はありません。つまり、次のものを削除します。

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

AJAX tabs(および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

今日、私は実際に同様のニーズを自分自身で見つけ、あなたが提供したコードが非常に有用であることがわかりました。私はあなたと同じ問題に遭遇しましたが、あなたが行方不明になっているのは、あなたがすべての後続のページであるのと同じ方法であなたのインデックスファイルまたはホームページを履歴にプッシュすることです。

これを解決するために私がやったことの例を次に示します(それが正しい答えであるかどうかはわかりませんが、それは簡単で動作します!):

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

お役に立てれば!

1
mattblac

これは古い質問ですが、このように簡単に状態を管理しようとするときは、次のアプローチを取る方が良いかもしれません。

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

このようにして、履歴に追加するときに状態オブジェクトにオプションのコールバック関数を指定でき、popstateがトリガーされると、この関数は状態オブジェクトをパラメーターとして呼び出されます。

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

これは、ニーズに合わせて簡単に拡張できます。

1
r3wt