对于tabs组件,使用href方式的时候,二次或者多次请求现象是很多人都遇到过的问题,尽管官方一再声明解决了相关bug,但是依旧一堆人会遇到二次请求的问题,每每分析下来我们都会发现大多数是自己代码的问题,而不是控件本身的问题。本篇讨论一个隐藏较深的似bug而非bug的二次请求问题。
Bug重现:
为了重现Bug,A页面我们定义一个tabs组件,并且初始化一个带有mini工具的tab页,mini工具栏里面包含一个小的刷新按钮用户刷新该标签页href指向的B页面的远程数据,在B页面我们alert一个消息出来,如果刷新的时候这个消息弹出两次,则说明刷新的时候进行了二次请求,请看演示中的标题为"tab1"的选项卡:
查看演示
原因分析:
为何使用tab对应panel的refresh方法刷新?
首先为了减少服务器负担,tab页的cache属性我是设置为true的,如果设置为false,每次选择一个tab页都会重新加载远程数据。
而在tab页chache为true的情况下,tabs组件自带的update方法又无法通过href获取新数据(原因请参照《正确对待panel和tabs组件的cache属性》),所以最终只能使用tab对应panel的refresh方法来更新tab页。
为何使用panel组件的refresh方法一定能刷新?
- function _1a0(_1a1) {
- var _1a2 = $.data(_1a1, "panel");
- if (_1a2.options.href && (!_1a2.isLoaded || !_1a2.options.cache)) {
- _1a2.isLoaded = false;
- _1a3(_1a1);
- var _1a4 = _1a2.panel.find(">div.panel-body");
- if (_1a2.options.loadingMessage) {
- _1a4.html($("<div class=\"panel-loading\"></div>")
- .html(_1a2.options.loadingMessage));
- }
- $.ajax({
- url : _1a2.options.href,
- cache : false,
- success : function(data) {
- _1a4.html(_1a2.options.extractor.call(_1a1, data));
- if ($.parser) {
- $.parser.parse(_1a4);
- }
- _1a2.options.onLoad.apply(_1a1, arguments);
- _1a2.isLoaded = true;
- }
- });
- }
- };
这是1.3.1的源码,这个_1a0方法就是panel组建请求远程数据的核心接口,我们发现只有当:
- if(_1a2.options.href && (!_1a2.isLoaded || !_1a2.options.cache)
条件满足时,才会请求数据,也就说,在设置了href属性的前提下,_1a2.options.cache或者_1a2.isLoaded其中一个为false的时候就请求数据。
我们再来来看看panel组件的refresh方法是怎么定义的:
- refresh:function(jq,href){
- return jq.each(function(){
- $.data(this,"panel").isLoaded=false;
- if(href){
- $.data(this,"panel").options.href=href;
- }
- _1a0(this);
- });
- }
首先就强制赋值$.data(this,"panel").isLoaded=false,正是这个强制赋值使得前面的_1a2方法必定会请求远程数据,这也是panel组件的refresh方法不管cache属性为什么均能刷新远程数据的秘密所在。
为何tab页对应panel的cache为false时,每次选择该tab都会请求远程数据?
我们仅仅分析tabs组件标题div元素绑定的事件的一段代码便可略见一斑了:
- //_2ba函数返回的是用户正在点击的tab页
- var _2c8=_2ba(_2c5,_2c6);
- if(!_2c8){
- return;
- }
- //_298函数是找到当前被选中的tab页,所以_2c9为当前选中页。
- var _2c9 = _298(_2c5);
- if (_2c9) {
- //关闭已经选中的tab页
- _2c9.panel("close");
- _2c9.panel("options").tab.removeClass("tabs-selected");
- }
- //打开用户正在点击的tab页
- _2c8.panel("open");
原因就在_2c8.panel("open");这句代码了,在panel组件中,打开关闭状态的panel时会尝试重新获取远程数据,决定是否重新获取的条件依旧是_1a0核心接口里面的if(_1a2.options.href && (!_1a2.isLoaded || !_1a2.options.cache)条件。
这样就很明显,当cache为false的时候,只要是href方式,点击tab页肯定就会重新请求远程数据。同时还有一点很重要,如果_1a2.isLoaded因为某种原因被设置为false了,也必然会导致请求远程数据。
点击刷新图标时,二次请求是如何发生的呢?现在可以总结出来了:
1、当你单击刷新图标时,图标绑定了panel组件的refresh方法进行ajax请求,在ajax返回数据之前,_1a2.isLoaded被设置为false;
2、同时因为mini的刷新图标绑定事件时并未阻止事件冒泡(阻止的话,点击小图标就无法自动选中tab页了);
3、当事件冒泡到tab页的标题元素时,被tab页标题元素绑定的事件处理程序接住处理,而这个事情处理程序会根据条件决定是否请求远程数据;
4、因为冒泡的速度肯定比ajax的速度快,这时候_1a2.isLoaded依旧为false,所以3中提到的事件处理程序很happy地也去请求远程数据了。
5、到此,二次请求问题就顺利的发生了,分析了一大堆,不知道各位是否明白了,本人表达能力有限,看不懂就多看两遍吧,或者qq群交流。
解决思路:
既然原因找到了,解决的思路也就很简单了:阻止事件冒泡,并且用脚本选中点击的tab页便可。对于如何阻止事件冒泡,不少人可能不清楚,其实网上这方面资料都一大堆,这里我就在罗嗦点,再提一下。
tab panel的tools为string时:
这种情况也就是小工具是引用页面内某个DOM生成的,比如:
- <div id="p-tools-2">
- <a href="#" id="a2" class="icon-mini-refresh" onclick="refresh2(event)"></a>
- </div>
这种方式往往使用onclick属性绑定事件的方式,我们需要使用原生的javascript阻止事件冒泡:
- function refresh2(event) {
- $('#tt').tabs('select', 'tab2');
- $('#tt').tabs('getTab', 'tab2').panel('refresh', '061_href_data.html');
- if (event && event.stopPropagation) {
- event.stopPropagation();
- } else {
- window.event.cancelBubble = true;
- }
- }
当然了,我们也可以不通过onclick属性绑定事件处理程序,而是通过jQuery绑定,这时候只要用jQuery封装好的函数阻止事件冒泡就行了:
- <div id="p-tools-3">
- <a href="#" id="a3" class="icon-mini-refresh"></a>
- </div>
- $(document).ready(function() {
- $('#a3').click(function(e) {
- $('#tt').tabs('select', 'tab3');
- e.stopPropagation();
- $('#tt').tabs('getTab', 'tab3').panel('refresh', '061_href_data.html');
- });
- });
tab panel的tools为对象时
这种情况就是用脚本生成小工具条的,比如:
- $('#tt').tabs('add', {
- title: 'tab4',
- closable: true,
- bodyCls: 'bodyCls',
- cache: true,
- tools: [{
- iconCls: 'icon-mini-refresh',
- handler: function(e) {//1.3.2的easyui并没有传入e这个事件参数,需求特殊处理一下
- $('#tt').tabs('select', 'tab4');
- e.stopPropagation();
- $('#tt').tabs('getTab', 'tab4').panel('refresh', '061_href_data.html');
- $('#tt').tabs('getTab', 'tab4').panel('options').content = undefined;
- }
- }]
- });
因为这种方式的handler无法获取到事件对象,所以要先修复一下,请参照这里:
http://www.easyui.info/archives/1108.html
修复过后再使用jQuery阻止事件冒泡就很容易了。
效果演示:
http://www.easyui.info/easyui/demo/tabs/061.html
演示中的tab1是没有阻止事件冒泡的情况,tab2,tab3,tab4均是阻止了事件冒泡的情况,只不过tab2,tab3,tab4的事件绑定方式不一样,我都一一举例了。
管理员 世纪之光 : 2013年03月08日00:06:33 地下1层
管理员 世纪之光 : 2014年03月14日13:34:08 地下1层