现在的位置: 首页 > easyui > Layout > tabs > 正文
事件冒泡引起的tabs组件二次请求
2012年10月24日 tabs ⁄ 共 4751字 评论数 5 ⁄ 被围观 25,008 views+
文章目录
[隐藏]

对于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方法一定能刷新?
  1. function _1a0(_1a1) {   
  2.     var _1a2 = $.data(_1a1, "panel");   
  3.     if (_1a2.options.href && (!_1a2.isLoaded || !_1a2.options.cache)) {   
  4.         _1a2.isLoaded = false;   
  5.         _1a3(_1a1);   
  6.         var _1a4 = _1a2.panel.find(">div.panel-body");   
  7.         if (_1a2.options.loadingMessage) {   
  8.             _1a4.html($("<div class=\"panel-loading\"></div>")   
  9.                     .html(_1a2.options.loadingMessage));   
  10.         }   
  11.         $.ajax({   
  12.             url : _1a2.options.href,   
  13.             cache : false,   
  14.             success : function(data) {   
  15.                 _1a4.html(_1a2.options.extractor.call(_1a1, data));   
  16.                 if ($.parser) {   
  17.                     $.parser.parse(_1a4);   
  18.                 }   
  19.                 _1a2.options.onLoad.apply(_1a1, arguments);   
  20.                 _1a2.isLoaded = true;   
  21.             }   
  22.         });   
  23.     }   
  24. };  

这是1.3.1的源码,这个_1a0方法就是panel组建请求远程数据的核心接口,我们发现只有当:

  1. if(_1a2.options.href && (!_1a2.isLoaded || !_1a2.options.cache)  

条件满足时,才会请求数据,也就说,在设置了href属性的前提下,_1a2.options.cache或者_1a2.isLoaded其中一个为false的时候就请求数据。

我们再来来看看panel组件的refresh方法是怎么定义的:

  1. refresh:function(jq,href){   
  2.     return jq.each(function(){   
  3.         $.data(this,"panel").isLoaded=false;   
  4.         if(href){   
  5.             $.data(this,"panel").options.href=href;   
  6.         }   
  7.         _1a0(this);   
  8.     });   
  9. }  

首先就强制赋值$.data(this,"panel").isLoaded=false,正是这个强制赋值使得前面的_1a2方法必定会请求远程数据,这也是panel组件的refresh方法不管cache属性为什么均能刷新远程数据的秘密所在。

为何tab页对应panel的cache为false时,每次选择该tab都会请求远程数据?

我们仅仅分析tabs组件标题div元素绑定的事件的一段代码便可略见一斑了:

  1. //_2ba函数返回的是用户正在点击的tab页   
  2. var _2c8=_2ba(_2c5,_2c6);   
  3. if(!_2c8){   
  4.     return;   
  5. }   
  6. //_298函数是找到当前被选中的tab页,所以_2c9为当前选中页。   
  7. var _2c9 = _298(_2c5);   
  8. if (_2c9) {   
  9.     //关闭已经选中的tab页   
  10.     _2c9.panel("close");   
  11.     _2c9.panel("options").tab.removeClass("tabs-selected");   
  12. }   
  13. //打开用户正在点击的tab页   
  14. _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生成的,比如:

  1. <div id="p-tools-2">  
  2.     <a href="#" id="a2" class="icon-mini-refresh" onclick="refresh2(event)"></a>  
  3. </div>  

这种方式往往使用onclick属性绑定事件的方式,我们需要使用原生的javascript阻止事件冒泡:

  1. function refresh2(event) {   
  2.     $('#tt').tabs('select', 'tab2');   
  3.     $('#tt').tabs('getTab', 'tab2').panel('refresh', '061_href_data.html');   
  4.     if (event && event.stopPropagation) {   
  5.         event.stopPropagation();   
  6.     } else {   
  7.         window.event.cancelBubble = true;   
  8.     }   
  9. }  

当然了,我们也可以不通过onclick属性绑定事件处理程序,而是通过jQuery绑定,这时候只要用jQuery封装好的函数阻止事件冒泡就行了:

  1. <div id="p-tools-3">  
  2.     <a href="#" id="a3" class="icon-mini-refresh"></a>  
  3. </div>  
  1. $(document).ready(function() {   
  2.     $('#a3').click(function(e) {   
  3.         $('#tt').tabs('select', 'tab3');   
  4.         e.stopPropagation();   
  5.         $('#tt').tabs('getTab', 'tab3').panel('refresh', '061_href_data.html');   
  6.     });   
  7. });  
tab panel的tools为对象时

这种情况就是用脚本生成小工具条的,比如:

  1. $('#tt').tabs('add', {   
  2.     title: 'tab4',   
  3.     closable: true,   
  4.     bodyCls: 'bodyCls',   
  5.     cache: true,   
  6.     tools: [{   
  7.         iconCls: 'icon-mini-refresh',   
  8.         handler: function(e) {//1.3.2的easyui并没有传入e这个事件参数,需求特殊处理一下   
  9.             $('#tt').tabs('select', 'tab4');   
  10.             e.stopPropagation();   
  11.             $('#tt').tabs('getTab', 'tab4').panel('refresh', '061_href_data.html');   
  12.             $('#tt').tabs('getTab', 'tab4').panel('options').content = undefined;   
  13.         }   
  14.     }]   
  15. });  

因为这种方式的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的事件绑定方式不一样,我都一一举例了。

目前有 5 条留言 其中:访客:2 条, 博主:2 条 引用: 1

  1. 螃蟹 : 2013年03月07日17:12:59  -49楼 @回复 回复

    楼主写一下解决方法额,新手,不知道怎么阻止冒泡,看到好多人都是把tab的href换成content解决的


    • 管理员
      世纪之光 : 2013年03月08日00:06:33  地下1层 @回复 回复

      解决的详细方式已经更新到文章中,请仔细阅读。

  2. 小贝 : 2014年03月13日21:41:51  -48楼 @回复 回复

    我的版本是jQuery EasyUI 1.3.4,确实有e参数,但是阻止不了选择panel的事件( e.stopPropagation();)原因也是因为事件冒泡导致的,为什么open会重新加载呢,原因是open之前你调refresh,此函数将变量isLoaded设置false了等加载完成后才设置true,open正好是在这个加载完成后触发的,导致open的时候属性isLoaded为false也进入的重新获取内容。如果加载panel内容是同步加载就不会有问题,调试的时候在open那停一会也不会有问题,所以刷新内容只要写一句话即可将isLoaded设置false。
    $(‘#menu-tabs’).tabs(‘getTab’, title).data(“panel”).isLoaded = false;
    利用open重新加载内容啦!


    • 管理员
      世纪之光 : 2014年03月14日13:34:08  地下1层 @回复 回复

      “tools的handler没有传入事件参数”这个问题,1.3.2之前是存在的,1.3.5版本同样存在。不过1.3.3和1.3.4两个版本,我自己没有亲自测试过,有时间的话,我会测试一下。

给我留言

留言无头像?


×