如何将AJAXy页面更新与“后退”按钮混合,以便在用户返回时仍然存在更新?

时间:2012-03-19 18:25:46

标签: html ajax browser back-button

我有一个用户可以修改的页面。所有修改都是使用JQuery执行的,并且还发送到服务器,因此完全重新加载也会生成修改后的页面。

这在Windows上的Firefox 11 / Chrome中运行良好:即使用户在其他地方导航然后使用“返回”按钮,他们也会获得包含最新编辑内容的页面。

但是,如果我现在将Google地图嵌入到页面中,则“后退”按钮将停止工作:它会在用户完成所有编辑之前将其显示在页面上。除了在浏览器的缓存中,此页面甚至不再存在,但它仍会显示。

我整理了一个显示此行为的简单测试用例 here

是什么给出的?我怎样才能解决这个问题?完美的解决方案只允许浏览器返回而不重新加载页面,就像通常那样。

P.S。显然,“工作”示例实际上并不适用于OSX上的Chrome。我如何解决浏览器坚持回到页面陈旧版本的问题?

错误报告描述了此行为:Firefox


Bounty :Windows上的Firefox和Chrome展示了两种行为(在一种情况下返回修改后的DOM,但在另一种情况下不再修改)。是否有描述浏览器 应该做什么的规范?是否存在以这种或那种方式改变的错误?这个问题有一个我可以google的通用名称吗?

我正在考虑通过JavaScript更新隐藏元素的解决方案,然后检查更新是否仍然存在。如果是这样,“后退”按钮恢复了最新的DOM,并且不需要做任何其他事情。如果没有,浏览器恢复过时的DOM,我可以强制重新加载页面,就像那样令人不快。对这种方法的任何评论也是受欢迎的。

注意:真实网站有比这更可编辑的控件,其中一个是自由格式文本区域。即使用户刚刚添加了几段文本,我也希望提议的解决方案能够正常工作。例如,在#之后,无法将此类内容附加到网址。

7 个答案:

答案 0 :(得分:5)

将Google地图嵌入到页面中会禁用bfcache(我会将mozilla术语用于缺少标准术语),因为<iframe>中加载的地图页面使用unload侦听器。

MDN上列出了在Firefox中快速返回导航页面未被缓存的可能原因:Using Firefox 1.5 caching。您的问题被列为“顶级页面包含不可缓存的帧”,这令人困惑,我将在稍后澄清。 (其他浏览器可能使用类似的启发式方法,因为这些规则是为了避免破坏现有内容而开发的 - 另请参阅this answer,它有一些链接。)

解决这个问题的正确方法是与Google上的某个人交朋友,然后唠叨他们,直到他们至少从地图的嵌入页面移除onunload听众。

一般情况下,您不应该依赖bfcache工作或不为特定页面工作。这只是对常见情况的优化。由于它是优化,因此可以禁用它,例如当系统内存不足时。如果用户在返回之前重新启动浏览器,或者关闭选项卡并选择“撤消关闭选项卡”,它也将无效,正如您在错误中所述。

您应该从JS恢复页面的状态,或者将页面标记为不可缓存(使用HTTP标头)。当然,前者可以带来更好的用户体验。 @Adam Gent的建议看起来是正确的,我将不得不检查他引用的Firefox问题。


bfcache以这种方式工作的原因是:

  • 如果浏览器运行onunload处理程序,然后通过bfcache恢复页面,页面可能会被破坏,因为脚本经常会删除onunload处理程序中的事件侦听器(“清理”,这在旧的情况下并不是必需的IE版本)
  • 如果浏览器停止在页面中运行onunload处理程序,因为用户可能会返回页面并且他们想要缓存它,那么作者会抱怨。
  • 如果iframe中的页面无法缓存,则恢复缓存的外页并重新加载内部页面有时会破坏它们(例如,如果两个页面都是同一个域,则外部页面可以保存对框架中对象的引用,重新加载内部框架后无效)。因此,如果没有缓存iframe,父页面也不会。

当您点击“返回”时,页面仍然从(磁盘)缓存加载的原因是,您可能已指定可以缓存发送到浏览器的内容。浏览器无法知道您是否更改了服务器上的页面,并对其进行了DOM更改。

希望这有帮助。


[edit]我将详细说明上面的“将页面标记为不可缓存”的想法。要使Web浏览器缓存为您而不是对您起作用,重要的是要记住HTTP是用于检索资源的协议。例如,由URL http://bbb.akshell.com/broken标识的HTML页面是资源。当您通过HTTP提供资源时,您可以指定浏览器资源副本的有效期(即匹配服务器上资源的规范版本)。

当您的测试用例中的资源是一个HTML页面,其中用户选择的项目以特殊方式标记时,资源可能随时更改(每次用户更改选择时)。这意味着在提供此资源时的诚实HTTP响应将是“不缓存,可能随时更改”。然后,每当需要加载页面时,浏览器就会从服务器重新加载页面 - 正确的行为,但代价是用户的速度慢。

另一种适用于在多个页内标签之间切换的方法是将每个选项与其自己的URL相关联。具有两个选项卡的页面将对应于两个资源(和两个URL) - 每个选项卡一个。两种资源都可以由浏览器缓存(HTTP-)。更改URL和页面内容可以在没有通过pushState到服务器的往返的情况下实现。

另一种方法似乎更适用于您的情况,您可以在其中保存用户在服务器上的输入:将应用UI和用户数据分成不同的资源(即使用JS的静态(HTTP)可缓存HTML页面,从单独的不可缓存的URL加载用户数据)。请求用户数据并在加载时更新UI。[/ edit]

答案 1 :(得分:1)

如果您不关心旧浏览器,可以使用本地存储和pushstate的组合。

使用@ lucian.pantelimon的解决方案+ pushstate + localstorage可以得到你想要的东西。

<script>
window.addEventListener("popstate", function(e) {
    alert("hello"); // do my restoration from localstorage.
});
</script>

@TimWi是对的,如果浏览器没有发起你的那种“屎溪”。

您可能想要调查的一件事是对框架集的真正严重破解(ala reddit / google images / linkedin文章样式)。

编辑:当远程站点上的后退按钮返回到您的站点时,看起来不会触发pushstate事件。

编辑2:看起来我的firefox v 10.0与pushstate有问题。它在chrome中运行良好。

答案 2 :(得分:1)

这个技巧将清除FireFox的bfcache:

   window.onunload = function(){}

答案 3 :(得分:0)

您的“工作”页面也不适用于我 - 至少不是您想要的方式。

看起来您通过jQuery更新页面的DOM,然后将更新发送回服务器,但从不强制浏览器从服务器获取最新信息。因此,后退按钮的行为完全取决于浏览器决定是否缓存最后一个服务器请求,或者是DOM留在的最后一个状态。

当您通过jQuery执行DOM更新时,您可以尝试使用查询字符串更新URI。 可能强制浏览器在返回时抓取最新信息。

答案 4 :(得分:0)

假设您不信任浏览器,根据更改的难度,您可以使用链接名称来指示页面状态。

例如,你的“选择我!”链接#3将导致http://bbb.akshell.com/broken#link3。这不会使页面重新加载。因此,即使在按下“返回”后从头开始加载页面,您仍然在URL中有#link3,您可以使用它来使页面进入您想要的状态(在这种情况下,点击“选择我!” #3)。

您还可以生成一些更复杂的网址,以弥补更复杂的更改。我相信某些类似的机制用于导航到flash网站的特定部分,当所有内容都由同一部Flash影片处理时。

P.S。这就是“我不相信浏览器”的解决方案。如果您确实信任 - 需要一个适用于其中每个方案的解决方案,我也有兴趣看到它。

答案 5 :(得分:0)

您可以尝试在Cookie中存储尽可能多的信息,并在需要时“重建”页面。

不是很干净的方法,因为每次访问页面时都会发送cookie(我认为限制cookie中存储的数据量是必须的),但它应该是跨浏览器兼容的。 / p>

另一种方法是将数据存储在window.name中。对于这种方法的优点,缺点和几个替代方案,请查看question 203075

编辑: 存储应用程序的状态并将id与每个ID相关联,然后将#stateID<state id here>之类的内容添加到地址中可能很有用。当用户单击后退按钮时,您可以知道要加载哪个状态。

I.E。:

说明您的州包含selectMeItemSelectedmapInfo{latitude, longitude, zoomLevel}信息。

当用户首次进入网站时,您在Cookie中保存stateID #1 (或其他地方,但我将在此示例中使用Cookie):

 state[1].selectMeItemSelected = 1;
 state[1].mapInfo.latitude = 45;
 state[1].mapInfo.longitude = 45;
 state[1].mapInfo.zoomLevel = 2;

在此之后,您将#stateID1添加到网站地址。

当用户执行激活您的AJAX代码的操作时,您将其他状态存储在Cookie中,将URL更改为#stateID2,然后继续运行您的AJAX代码。

当您检测到用户点击了后退(或前进)按钮时,只需使用状态中存储的信息加载页面。

如果按顺序生成stateID,则还应在添加新状态之前清除当前状态之后的状态。通过这种方式,您可以获得浏览器中后退和前进按钮的行为:如果您从 stateX 单击后退,然后单击另一个链接(可以是AJAX),您就赢了“ t有一个活动的转发按钮处于活动状态,点击“返回” stateX ,您可以考虑清除该状态。

此方法不适用于书签,除非您将状态存储在服务器上并每次传递状态信息或实现某种永久链接。

我希望我已经成功地表达了我的想法,因为当我重读它时似乎有点不清楚。如果您有疑问,请发表评论(如果您发现此信息有用,或者课程)。

希望有所帮助:)

答案 6 :(得分:0)

我不知道当您按下“返回”按钮时,所有浏览器是否都会运行domready事件(或者确实是任何事件),它会将您带到缓存页面。很明显如果浏览器没有运行任何事件,那么你无能为力。

然而,我怀疑他们运行domready事件。如果他们没有,那么通过$(function() { ... })添加的jQuery事件挂钩将不会被设置,如果是这种情况,我可能会遇到更多错误(因为它会更加明显我作为用户)。

因此,我的建议是处理windowunload†事件;在该事件中,将一些代码挂钩到domready事件,该事件检查页面是处于修改状态还是原始未修改状态;如果它处于未修改状态,则强制重新加载页面。 (理论上,你可以以aja方式重新加载,以免影响浏览器历史记录。)

因此,您需要将页面标记为已修改或未修改。例如,您可以在每次修改页面时向body元素添加属性。在jQuery中,我建议使用data

$(document.body).data('modified', true);

当然缺点是您需要在每个 ajaxy修改页面的代码片段中执行此操作。如果你有一个集中的功能,你所有的ajaxy DOM修改都会通过,那就容易多了。

这是我对windowunload†处理程序的尝试:

$(window).unload†(function() {
    $(function() {
        if (!$(document.body).data('modified'))
            forceReload();
    });
});

当然你必须自己写forceReload()。它可以是一个简单的location.reload‡()或一个ajaxy来电。

†我不记得事件的确切名称或挂钩的jQuery函数。

‡同样,检查它是否被称为reloadrefresh或其他。