Primefaces的overlayPanel存在延迟问题 - 加载过于懒惰

9

我正在使用Primefaces 3.2、JSF 2和Glassfish 3.1.2。

我有一个用户的p:dataTable,其中包含用户的头像。每当用户将鼠标移动到头像上时,就会出现一个显示更多信息(延迟加载)的p:overlayPanel,并在用户移动光标时消失 - 就像这样:

<p:overlayPanel for="avatar" dynamic="true" showEvent="mouseover" hideEvent="mouseout" ...>

这很有效 - 只要用户“慢手”。当用户快速移动光标时,许多overlayPanels保持可见状态。 例如,当用户将光标放在显示用户头像的位置上并使用鼠标滚轮向上或向下滚动用户表格时。

我认为overlaypanel会在分派showEvent="mouseover" 时从服务器动态加载信息(dynamic="true"),并在服务器返回响应后显示overlaypanel。 这样就无法检测到当overlaypanel变得可见时鼠标是否已经移开,因此hideEvent="mouseout" 永远不会被分派。

有没有办法使primefaces overlaypanel在鼠标悬停时直接出现,显示loading gif并在响应来自服务器后更新overlaypanel中的内容。

这是一个好方法吗?还是有其他方法解决这个麻烦的问题?

谢谢Pete


你很幸运能够得到内容。而我遇到完全相同的问题,我的内容也完全消失了。即使是一个简单的h:outputText,没有引用实际行模型(那个你用var指定的)也无法显示。看到primefaces的开发人员甚至没有考虑到一个非常普遍的企业问题(在我看来,p:overlayPanel或p:tooltip似乎并没有被设计用于表格),真是令人悲哀。 - Sebastian Hoffmann
你用Firebug测试过工作流程了吗?检查JS错误,还要检查来自服务器的ajax响应内容。 - Del Pedro
是的,已发送一个ajax请求。这真的很奇怪;虽然我没有使用3.3版本,而是最新的3.4快照。这可能是一个新的bug。 - Sebastian Hoffmann
当将完全相同的标记放在表格外部时,一切都按预期工作。我现在可能会使用可扩展行。我希望这些错误会在3.4发布时得到修复。 - Sebastian Hoffmann
4个回答

5

由于我的第一个答案已经很长并且包含有效的信息,所以我决定开一个新的回答,介绍我的最终方法。

我现在使用Primefaces继承模式使代码更加简洁。此外,我注意到不必替换/覆盖整个 bindEvents 函数,因为我们可以删除旧的事件处理程序。最后,这段代码修复了最新遇到的问题:ajax到达之前的隐藏事件。

PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.OverlayPanel
        .extend({

            bindEvents : function() {
                this._super();

                var showEvent = this.cfg.showEvent + '.ui-overlay', hideEvent = this.cfg.hideEvent
                        + '.ui-overlay';

                $(document).off(showEvent + ' ' + hideEvent, this.targetId).on(
                        showEvent, this.targetId, this, function(e) {
                            var _self = e.data;

                            clearTimeout(_self.timer);
                            _self.timer = setTimeout(function() {
                                _self.hidden = false;
                                _self.show();
                            }, 300);
                        }).on(hideEvent, this.targetId, this, function(e) {
                    var _self = e.data;

                    clearTimeout(_self.timer);
                    _self.hidden = true;
                    _self.hide();

                });
            },

            _show : function() {
                if (!this.cfg.dynamic || !this.hidden) {
                    this._super();
                }
            }

        });

抱歉格式有些混乱,是Eclipse的问题。 我很抱歉格式较差,这是Eclipse的故障。

2

哇,经过长时间的调试和尝试各种方法后,我认识到问题不在于Ajax请求,而是事件处理程序本身:

.on(hideEvent, this.targetId, this, function(e) {
            var _self = e.data;

            if(_self.isVisible()) {
                _self.hide();
            }
        });

正如您所看到的,如果小部件在之前可见,那么它只是被隐藏了。如果您将鼠标移出得太快,现在可能会发生两件事情:

  • 小部件根本不可见
  • 动画仍在进行中

在这种情况下,事件被丢弃,面板保持可见状态。由于动画被排队,因此只需删除if语句即可解决问题。我通过替换整个bindEvents方法来实现此操作:

PrimeFaces.widget.OverlayPanel.prototype.bindEvents =  function() {
    //mark target and descandants of target as a trigger for a primefaces overlay
    this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);

    //show and hide events for target
    if(this.cfg.showEvent == this.cfg.hideEvent) {
        var event = this.cfg.showEvent;

        $(document).off(event, this.targetId).on(event, this.targetId, this, function(e) {
            e.data.toggle();
        });
    }
    else {
        var showEvent = this.cfg.showEvent + '.ui-overlay',
        hideEvent = this.cfg.hideEvent + '.ui-overlay';

        $(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
            var _self = e.data;

            if(!_self.isVisible()) {
                _self.show();
            }
        })
        .on(hideEvent, this.targetId, this, function(e) {
            var _self = e.data;

            _self.hide();

        });
    }

    //enter key support for mousedown event
    this.bindKeyEvents();

    var _self = this;

    //hide overlay when mousedown is at outside of overlay
    $(document.body).bind('mousedown.ui-overlay', function (e) {
        if(_self.jq.hasClass('ui-overlay-hidden')) {
            return;
        }

        //do nothing on target mousedown
        var target = $(e.target);
        if(_self.target.is(target)||_self.target.has(target).length > 0) {
            return;
        }

        //hide overlay if mousedown is on outside
        var offset = _self.jq.offset();
        if(e.pageX < offset.left ||
            e.pageX > offset.left + _self.jq.outerWidth() ||
            e.pageY < offset.top ||
            e.pageY > offset.top + _self.jq.outerHeight()) {

            _self.hide();
        }
    });

    //Hide overlay on resize
    var resizeNS = 'resize.' + this.id;
    $(window).unbind(resizeNS).bind(resizeNS, function() {
        if(_self.jq.hasClass('ui-overlay-visible')) {
            _self.hide();
        }
    });
};

在加载时执行此代码,问题就会消失。



尽管您要替换js代码,但您可以利用这个机会实现一个非常好的功能。通过在事件处理程序中使用超时,可以轻松地实现一个小延迟,不仅可以提高用户体验(不再出现成千上万的弹出窗口),而且还可以减少网络流量:

        $(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
            var _self = e.data;

            _self.timer = setTimeout( function(){
                if(!_self.isVisible()) {
                    _self.show();
                }
            }, 300);
        })
        .on(hideEvent, this.targetId, this, function(e) {
            var _self = e.data;

            clearTimeout(_self.timer);
            _self.hide();

        });

当然,你可以使用全局变量来控制延迟时间。如果你想要更加灵活的方法,你需要重写OverlayPanelRender中的encodeScript方法以传输额外的属性。然后你可以通过_self.cfg.delay来访问它。但请注意,你还需要替换组件模型OverlayPanel并为其提供额外的属性。


听起来好像又好又坏,感谢你的努力。我会查看并稍后回复。 - Del Pedro
我正在使用3.4版本的快照。我认为它来自10.8版本,但我不太确定。通过更新,修复了一些我在3.3版本中遇到的错误。其中最显著的可能是p:menuitem中f:param的支持。 - Sebastian Hoffmann
似乎3.2版本的实现略有不同。您可以在http://primefaces.googlecode.com/svn/primefaces/tags/3_2_RC1/src/main/resources/META-INF/resources/primefaces/overlaypanel/overlaypanel.js上查看它。但基本上您需要做相同的事情。 - Sebastian Hoffmann
我已经下载了完整的源代码,现在我正在调整_self.isVisible()的功能,然后我会知道更多。谢谢 - Del Pedro
非常奇怪,因为我没有遇到这些问题。我猜你得跟踪组件是否已被隐藏,并在满足该条件时不显示它。在调用 self._show() 之前,在 showEvent 处理程序中重置此属性即可。您可以将 _showhide 方法存储在某个地方,并在覆盖的 _showhide 方法中调用它们。这样,您就不必替换整个代码了。 - Sebastian Hoffmann
显示剩余2条评论

2

同时,我感谢您提供的这个绝妙解决方案。在此机会下,我更新了它,以适用于Primefaces 5.2。在我们的应用中,在升级后,代码出现了问题。

以下是更新的代码,适用于Primefaces 5.2

    PrimeFaces.widget.OverlayPanel.prototype.bindTargetEvents =  function() {
    var $this = this;
    //mark target and descandants of target as a trigger for a primefaces overlay
    this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);

    //show and hide events for target
    if(this.cfg.showEvent === this.cfg.hideEvent) {
        var event = this.cfg.showEvent;

        this.target.on(event, function(e) {
            $this.toggle();
        });
    }
    else {
        var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
        hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';

        this.target
            .off(showEvent + ' ' + hideEvent)
            .on(showEvent, function(e) {
                clearTimeout($this.timer);

                $this.timer = setTimeout(function() {
                    $('.ui-overlaypanel').hide(); 
                    $this.hidden = false;
                    $this.show();
                }, 500);
            })
            .on(hideEvent, function(e) {

                clearTimeout($this.timer); 

                $this.timer = setTimeout(function() {
                    // don't hide if hovering overlay
                    if(! $this.jq.is(":hover")) {
                        $this.hide();
                    }
                }, 100);
            });
    }

    $this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel').on('keydown.ui-overlaypanel', function(e) {
        var keyCode = $.ui.keyCode, key = e.which;

        if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
            e.preventDefault();
        }
    })
    .on('keyup.ui-overlaypanel', function(e) {
        var keyCode = $.ui.keyCode, key = e.which;

        if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
            $this.toggle();
            e.preventDefault();
        }
    });
};

我还添加了一个额外的功能,允许用户在不隐藏覆盖层的情况下移动鼠标。当您将鼠标移出它时,它应该自动隐藏,我是通过以下方式实现的:

<p:overlayPanel .... onShow="onShowOverlayPanel(this)" ...>

function onShowOverlayPanel(ovr) {
    ovr.jq.on("mouseleave", function(e) {
        ovr.jq.hide();
    });
}

希望您喜欢!


0

虽然时间有点久远,但如果有人遇到这个问题,可以使用 overlayPanelshowDelay 属性来解决这个问题从Primefaces 6.2开始。不过,由于某些原因,它并没有出现在官方文档中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接