Подтвердить что ты не робот

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

Я пытаюсь использовать API истории HTML5 с загрузкой содержимого ajax.

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

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

});

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Надеюсь, это поможет!

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

Надеюсь, я не ошибаюсь.