Ajax с историей.pushState и popstate-что делать, когда свойство popstate state равно null?

я опробую API истории HTML5 с загрузкой содержимого ajax.

У меня есть куча тестовых страниц, связанных относительными ссылками. У меня есть этот JS, который обрабатывает клики по этим ссылкам. При нажатии на ссылку обработчик захватывает атрибут href и передает его ajaxLoadPage(), который загружает содержимое с запрошенной страницы в область содержимого текущей страницы. (Мои PHP-страницы настроены на возврат полной HTML-страницы, если вы запросите их обычно, но только кусок довольна, если ?fragment=true добавляется к URL-адресу запроса.)

затем мой обработчик щелчка вызывает историю.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);
            }
        }
    });

});

когда вы добавляете URL-адреса в историю с pushState, вам также нужно включить обработчик событий для события popstate, чтобы иметь дело с щелчками по кнопкам назад или вперед. (Если вы этого не сделаете, щелчок назад покажет URL-адрес, который вы нажали в журнал в адресной строке, но страница не обновляется.) Поэтому мой обработчик popstate захватывает URL, сохраненный в свойстве state каждой созданной мной записи, и передает его ajaxLoadPage для загрузки соответствующего содержимого.

это работает нормально для страниц, которые мой обработчик кликов добавил в историю. Но что происходит со страницами, которые браузер добавил в историю, когда я попросил их "нормально"? Скажем, я обычно приземляюсь на свою первую страницу, а затем перемещаюсь по своему сайту с помощью кликов, которые делают эту загрузку ajax - если я попытаюсь вернуться через история к этой первой странице, последний щелчок показывает URL для первой страницы, но не загружает страницу в браузере. Почему так?

Я вижу, что это имеет какое-то отношение к свойству состояния этого последнего события popstate. Свойство state имеет значение null для этого события, поскольку только записи, добавленные в историю pushState() или replaceState (), могут дать ему значение. Но моя первая загрузка страницы была "нормальным" запросом - почему браузер не просто отступает и загрузить исходный URL нормально?

6 ответов


это более старый вопрос, но есть гораздо более простой ответ с использованием собственного javascript для этой проблемы.

для начального состояния вы не должны использовать history.pushState а history.replaceState.

все аргументы одинаковы для обоих методов с той лишь разницей, что pushState создает новую запись истории и, таким образом, является источником вашей проблемы. replaceState только заменяет состояние этой записи истории и будет вести себя так, как ожидалось, то есть вернуться к исходному стартовая страница.


я столкнулся с той же проблемой, что и исходный вопрос. Эта строка

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

следует заменить на

var initialPop = !popped;

этого достаточно, чтобы поймать начальный поп. Тогда вам не нужно добавлять исходную страницу в pushState. т. е. удалить следующее:

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

окончательный код на основе вкладок AJAX (и с использованием 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');
       }
   });
}

Я все еще не понимаю, почему кнопка "Назад" ведет себя так - я бы подумал, что браузер будет рад вернуться к записи, которая была создана обычным запросом. Возможно, когда вы вставляете другие записи с pushState, история перестает вести себя нормально. Но я нашел способ сделать свой код лучше. Вы не всегда можете зависеть от свойства state, содержащего URL-адрес, к которому вы хотите вернуться. Но шаг назад через историю изменяет URL-адрес в адресной строке, как вы бы ожидайте, так что это может быть более надежным для загрузки контента на основе окна.местоположение. После этого пример Я изменил обработчик popstate, чтобы он загружал контент на основе URL-адреса в адресной строке вместо поиска URL-адреса в свойстве state.

одна вещь, которую вы должны следить за тем, что некоторые браузеры (например, Chrome) запускают событие popstate, когда вы изначально попали на страницу. Когда это происходит, вы можете перезагрузить содержимое начальной страницы без необходимости. Так Я добавил несколько бит кода из excellent 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, - это только прикрепить обработчик событий click, если браузер поддерживает API истории.


Я на самом деле нашел себя с аналогичной потребностью сегодня и нашел код, который вы предоставили, очень полезным. Я пришел к той же проблеме, что и вы, и я считаю, что все, что вам не хватает, это толкать свой индексный файл или домашнюю страницу в историю так же, как вы все последующие страницы.

вот пример того, что я сделал, чтобы решить эту проблему (не уверен, что это правильный ответ, но это просто и работает!):

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

надеюсь, что это помогает!


Я понимаю, что это старый вопрос, но при попытке легко управлять состоянием, как это, может быть, лучше принять следующий подход:

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

вы можете легко расширить это в соответствии с вашими потребностями.


И Наконец говорит:

Я бы подумал, что браузер будет рад вернуться к записи, которая была создана обычным запросом.

Я нашел объяснение поведения этого странного браузера здесь. Объяснение

вы должны сохранить состояние, когда ваш сайт загружается в первый раз и после этого каждый раз, когда он меняет состояние

Я проверил это - работает.

Это означает, что нет необходимости в загрузка содержимого на основе окна.местоположение.

надеюсь, я не введу вас в заблуждение.