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?
Ответы
Ответ 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.
Надеюсь, я не ошибаюсь.