java局部刷新session过期_Ajax局部页面刷新和History API结合的陷阱

ajax在现代网站已经得到非常普遍地应用,主要的好处大家都知道(异步加载数据,不用刷新整个浏览器,更小的数据传输尺寸)。对于那些老网站或者老项目来说全盘改造成ajax并不现实,于是就有了“局部页面刷新”这个解决方案。如果不知道“局部页面刷新”是何物请看这里,这里和这里。

在我们的项目里,将原来的iframe或者frame统统替换成了时髦的div,然后修改了页面上所有发起请求的地方,把响应内容jQuery.load到div里。

于是乎原来老旧的网站变成了一个时髦的基于ajax的网站,每个页面传输的数据量变小了,再也不用解决令人头疼的:

如何访问parent window变量的问题(还有如何访问parent的parent的parent... window变量的问题)

如何访问child iframe里的变量[]的问题了(还有如何访问child的child的child... iframe里的变量的问题)

因为大家永远都在同一个window里,而且div本身就会根据内容自动撑大。但是等等!浏览器怎么不能后退了?

我们的那个项目是一个满大街可见的XX管理信息系统,这种系统最常见的布局就是左侧一个树形菜单区域,右侧是一个功能区域,功能区域里有一个查询条件区域(里面有个查询按钮),还有一个空白的区域用来显示查询结果,同时是用户操作数据的地方(比如form表单)。

在iframe时代,上面讲到的4个区域都是一个iframe,这也就意味着我们可以有很变态的后退能力。

当然了一般来说用户最常用的就是对操作区域做后退动作,比如查询一下,选择一条记录点击修改,看到form表单,修改一下,在点击保存前后悔了,点击浏览器的后退,回到查询结果页面。

但是在引入了ajax后无法后退了,因为ajax请求不会记录到浏览器历史里,历史都没有了自然就无法后退了。

好在Html5的History API能够帮助我们解决问题。我们可以人为的使用history.pushState来人造历史信息,并且通过监听popstate事件来知道用户点击了浏览器后退或前进按钮,然后将页面元素还原到历史上的某个状态。关于Html5 History API的相关信息可以看这里。

但是事情远不止这么简单,下面是我们遇到的一些坑:

陷阱1:重复执行js脚本

// 点击查询按钮的时候人为构造一个浏览器历史

$('#some-button').click(function() {

$(targetSelector).load(url);

history.pushState({

container : targetSelector,

content : $(targetSelector).html()

}, null, url);

});

// 当浏览器后退后者前进的时候,我们把当时的结果重新加载到container里来

window.addEventListener('popstate', function() {

var state = history.state

$(state.container).html(state.content);

})

一切看上去都OK,直到...我们发现局部页面刷新所获得的结果里包含了操作dom元素的js。

当遇到这种情况时会发生很奇妙的现象,history state.content是已经加载完毕+js执行后的结果,当我们重新还原的时候,我们会把这个结果加载出来,并且又会执行一遍js。如果这个js是一个添加dom的动作那么在后退的时候你会看到这个重复的dom元素。

我们想过跟踪哪些dom元素是被js修改过的来避免这个问题,但是...这是不现实的。

陷阱2:无法还原到最初状态

前面的方案因为load的内容里可能有js脚本所以有严重缺陷,于是我们换了个思路,history里保存responseText,而不是已经load好后的东西。

// 点击查询按钮的时候人为构造一个浏览器历史

$('#some-button').click(function() {

$(targetSelector).load(url, function(responseText) {

history.pushState({

container : targetSelector,

content : responseText

}, null, url);

});

});

// popstate事件的处理方式一样

但是仍然遇到了这么一个问题,如果container(刷新目标区域,某个div)原来是有内容的,而这个内容不是通过ajax局部页面刷新而来,而是用户一进入这个页面就已经有的,比如使用服务器端的模板引擎生成的页面,那么在它加载完html片段后就无法回退了。因为它的内容一开始就不在history里(事实上浏览器自己产生的history是没有state的),这样就形成了退无可退的局面。

如果你想,我们只要保存这个container原来的内容不就行了,当后退的时候我们直接恢复它原来的内容,但是请看陷阱1

不过当发生退无可退的情况时,我们认为已经退回到了第一次进入页面的状态,这个时候我们刷新整个页面就行了。

陷阱3:多个并列的container

陷阱2的解决方案实际上是基于container之间是属于嵌套关系或者就一个container的情况的。如果是这种情况就不行了:

有A和B两个container,点击某个按钮刷新了A的内容(产生历史),然后在点击某个按钮刷新的B的按钮(产生历史),按照用户的预想情况,第一次后退还原B原来的内容,第二次后退还原A原来的内容。但实际上,第一次后退无法还原B的内容(陷阱2),第二次后退页面刷新了(一切恢复最初的样子)。

如果B是嵌套在A里的就无所谓了,第一次后退的时候获得的是A的state,根据A的state还原A的内容的时候顺便把B也还原了,第二次后退页面刷新,把A也还原了。

而且根据陷阱1所讲,我们也不能在history里存储A或者B里原来的内容。

解决办法:对于这种操作就不要记录历史了。

陷阱4:看到过时页面

我们在History state里存的是当时load时的responseText,当我们后退的时候看到的是过时的页面,比如我们原先查询结果里看到有A记录,然后我们跳转到其他页面里,然后再后退到查询结果页面看到A记录还在,但是这个A记录很可能只是一个幽灵,在数据库里早就已经不存在了。如果我们这个时候再对A记录操作就有出现错误。

解决办法是我们在history state里保存url已经相关的参数,当popstate的时候重新发起请求就行了,这样一来的话也减少了history存储state所需要的空间。

// 这里只给get请求的例子,post的原理也差不多

$('#some-button').click(function() {

$(targetSelector).load(url, function(responseText) {

history.pushState({

container : targetSelector,

url : url

}, null, url);

});

});

window.addEventListener('popstate', function() {

var state = history.state;

$(state.container).load(state.url);

});

陷阱5:redirect

即使我们在history state保存了url你就以为没事了?too simple, too naive!如果我们对这个url发起的请求被服务器redirect到另一个url,那么在history state里保存这个url就不对了。

如果我们这个url是用来删除某条记录的,服务器收到请求在数据库里删除了这条记录,然后redirect到了首页url,那么这个时候你在history里应该存那个url呢?显然是首页的url,因为如果你存了删除url,那么在后退的时候,我们会重新发起这个url,想想这多吓人。

解决办法其实不太简单,因为ajax是否被redirect你是不知道的,用jQuery封装的jqXHR对象也没法知道这个。

我的做法在服务器sendRedirect之前在requestUrl的queryString里添加一个flag,用一个专门的servlet filter判断过来的请求是否有这个flag,如果有那么就将本次请求的url(也就是redirect到的url)放到response的一个特定的header里。然后就可以用jqXHR.getResponseHeader('some-header')来获得这个url,把这个url放到history state里。

陷阱6:无法精确还原dom对象的状态

不论是保存responseText还是保存url请求参数,都无法在浏览器后退的时候精确还原dom对象的状态,比如我在IE6里有个这样的特性,你在某个页面勾选了某个checkbox,然后跳转到一个新的页面然后再后退,那个checkbox还是处于勾选状态,这个在利用ajax局部页面刷新里是完全做不到的,想到用户和我说以前后退的时候那个勾还在现在勾没有了,不解决这个BUG就不验收的事情时才想到iframe的好啊。

所以如果要精确还原dom对象的状态,得在history.pushState的时候自行把相关信息保存下来,在popstate的时候用到这些信息并还原dom。

事实上即使用了iframe也并不是所有的浏览器会还原dom对象状态,看这篇文章。

总结

不要轻易从iframe切换到ajax局部页面刷新

要自己控制那些ajax局部页面刷新纪录历史,哪些不记录,有些时候可能还需要replaceState,不要想当然的把所有请求都记录历史

把代码改造成ajax局部页面刷新只是第一步,还需要对整个网站、应用的UI做规划和设计,关于这个问题不存在通用的解决方案

参考资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/271823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java通过Netty,实现Websocket消息推送只需要简单几步

前言 曾几何时,不知道大家有没有在项目里遇到过需要服务端给客户端推送消息的需求,是否曾经苦恼过、纠结过,我们知道要想实现这样的需求肯定离不开websocket长连接方式,那么到底是该选原生的websocket还是更加高级的netty框架呢&a…

53.Maximum Subarray

/** 53.Maximum Subarray * 2016-5-7 by Mingyang * 如果我们从头遍历这个数组。对于数组中的其中一个元素,它只有两个选择: 1.* 要么加入之前的数组加和之中(跟别人一组) * 2. 要么自己单立一个数组(自己单开一组&…

java 创建者设计模式_Java设计模式之创建者模式分享热爱编程,程序人生

PS:今天的23中设计模式中的创建者方式,至此告一段落。我今天带来的技术分享为创建者模式以及原型模式。当然在Java中这两种方式很常见,只不过我们写的次数确实有点低而已,但是这不是我不学它的借口!!!创建者…

一文读懂电感器的原理、结构、作用及分类

电感器是能够把电能转化为磁能而存储起来的元件。电感器的结构类似于变压器,但只有一个绕组。电感器具有一定的电感,它只阻碍电流的变化。 如果电感器在没有电流通过的状态下,电路接通时它将试图阻碍电流流过它;如果电感器在有电流…

final关键字与static对比

final关键字与static对比 static关键字修饰变量时,会使该变量在类加载时就会被初始化,不会因为对象的创建再次被加载,当变量被static 修饰时就代表该变量只会被初始化一次 例如图中所示,被static修饰的变量j,虽然创建…

juce中的BailOutChecker

界面库中值得注意的一点就是对象响应事件的时候自身被删除了,那么后续的访问自然就会出问题,所以需要在响应事件之后先添加引用,相关处理之后再查看自身是否已经被删除,如果已经被删除那么就直接退出。juce中通过BailOutChecker来…

java quartz 跳过_Java Quartz计划作业-禁止同时执行作业

我正在使用Quartz Job执行特定任务。我也在我的Main应用程序类中安排它的执行,而我试图完成的工作是不允许同时执行此作业的实例。因此,调度程序仅应在其先前实例完成后才执行作业。这是我的工作班级:public class MainJob implements Job {s…

mac USB串口工具配置

安装USB serial 驱动 我的usb serial芯片是 pl2303, 先到官网上下载对应驱动,并安装。安装完成之后会要求重启。 http://www.prolific.com.tw/admin/Technology/GetFile.ashx?fileID238 安装 minicom https://alioth.debian.org/projects/minicom/ 下载源码&…

macpro生成公钥并查看公钥

打开macpro的终端输入以下命令: $ cd ~/.ssh $ ls 此时发现没有那个id_rsa.pub文件,没有,就需要创建公钥 用ssh-keygen创建公钥 此时已经有了

java join 源码_join on 和where 一起使用的细节

left join :左连接,返回左表中所有的记录以及右表中连接字段相等的记录。right join :右连接,返回右表中所有的记录以及左表中连接字段相等的记录。inner join: 内连接,又叫等值连接,只返回两个表中连接字段相等的行。full join:外…

SSIS 学习之旅 FTP访问类

这章把脚本任务访问FTP的方法 全部给大家。 控件的使用大家如果有不懂得可以看下我之前的文章。第一章:SSIS 学习之旅 第一个SSIS 示例(一)(上) 第二章:SSIS 学习之旅 第一个SSIS 示例(二&#…

Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient

http 连接池能提升性能 http 的背景原理 a. 两台服务器建立 http 连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并且也很耗时间。 b. Http 连接需要的 3 次握手 4 次分手开销很大,这一开销对于大量的比较小的 http 消息来说更大。…

Java容器坐标起点_Java的屏幕坐标是以像素为单位,容器的左下角被确定为坐标的起点...

【单选题】【单选题】【单选题】class A{ int x1; void func1(int x1){ this.x1 x1; } } 关于上述程序,说法错误的是( )【单选题】浏览器的作用是( )。【判断题】构建大学生心理危机预警及干预工作机制,更好地帮助有严重心理问题的学生度过心理难关,及早预防、及时疏导、有效干…

自媒体工具:文本内容转音频文件实用小工具

目录 ​编辑 1、软件介绍 2、软件技术框架 3、使用说明 4、核心代码文件 5、注意事项 1、软件介绍 文本内容转转音频文件小工具,采用C#编程语言,基于Framework4.5开发,主要采用百度语音识别SDK,实现了在线文本内容转音频文件的功能…

IDEA 创建 SpringCloud项目-多项目方式

SpringCloud 虽然可以用多模块化的方式来创建,但是,SpirngCloud本身就是为分布式而准备的,如果使用多模块的话,那就是一个项目,偏离了分布式的概念。所以工程上还是常用多项目的方式,这样才可以分开布署各个…

php位运算重要吗,PHP位运算的用途

下面为大家带来一篇PHP位运算的用途。现在就分享给大家,也给大家做个参考。一起过来看看吧在实际应用中可以做用户权限的应用我这里说到的权限管理办法是一个普遍采用的方法,主要是使用到”位运行符”操作,& 位与运算符、| 位或运行符。参…

盘点6款实用的文件对比工具,你都用过吗?

❤️作者主页:IT技术分享社区 ❤️作者简介:大家好,我是IT技术分享社区的博主,从事C#、Java开发九年,对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉: 数据库领域优质创作者🏆&#x…

aggregations 详解1(概述)

aggregation分类 aggregations —— 聚合,提供了一种基于查询条件来对数据进行分桶、计算的方法。有点类似于 SQL 中的 group by 再加一些函数方法的操作。 聚合可以嵌套,由此可以组成复杂的操作(Bucketing聚合可以包含sub-aggregation&#…

IDEA开发中,类的头位置生成作者时间信息

点击 File > Settings > File and Code Templates > Class按照图中步骤添加如下信息 #if (${PACKAGE_NAME} && ${PACKAGE_NAME} ! "")package ${PACKAGE_NAME};#end #parse("File Header.java") /** * Author WangZeyu * Date ${…

提现接口网站 php,API提现接口

>获取提现积分的类型,在后台可以设置某种积分可被提现,此处获取的数据为可提现积分的类型~~~[api]get:/index.php/accounts/Apipoint/member_withdrawal_listint:type 0#是否智能限制提现积分类型,0:不智能,1&#…