Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Latency issue with Primefaces overlayPanel - loads to lazy

I am using Primefaces 3.2 with jsf 2 and glassfish 3.1.2.

I have a p:dataTable of users containing avatars of the user. Whenever the user moves the mouse over the avatar a p:overlayPanel appears with more information (lazy loaded) on the user, and disappears when the user moves the cursor away - like this:

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

This works very well - as long as the user is "slowhanded". Whenever an user moves the cursor fast above many avatars many of the overlayPanels stay visible. For example when the user has the cursor over the position where user avatars are displayed and uses the scroll wheel of his mouse to scroll the usertable down or up.

I believe that the overlaypanel starts to load the information dynamically (dynamic="true") from the server when showEvent="mouseover" is dispatched and displays the overlaypanel after the response from the server arrives. This way it is not possible to detect whether the cursor is already away when the overlaypanel becomes visible - so the hideEvent="mouseout" is never dispatched.

Is there a way to make the primefaces overlaypanel appear directly on mousover, showing a loading gif and update the content into the overlaypanel when the response comes from the server.

Is this a good appraoch or does anyone know any other way to solve this nasty problem?

Thanks Pete

like image 919
Del Pedro Avatar asked Aug 10 '12 11:08

Del Pedro


2 Answers

Wow, finally after a long debuging session and testing various approaches i recognized that the problem isnt the ajax request but the event handlers itself:

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

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

As you can see, the widget is just hidden if its visible before. If your moving your mouse out too fast, now two things can happen:

  • The widget isnt visible at all
  • The animation is still going on

In this case the event is discarded and the panel stays visible. As animations are queued, one simply has to remove the if statement to fix the issue. I did this by replacing the whole bindEvents method:

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();
        }
    });
};

Execute this code on load and the issue should be gone.



As your replacing the js code nevertheless, you can use this oppurtunity to implement quite a nice feature. By using timeouts in the event handlers one can easily implement a little delay not just improving usability (no more thousands of popups appear) but also reducing network traffic:

        $(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();

        });

Ofcourse you can use a global variable to control the delay time. If you want a more flexible approach youll have to overwrite the encodeScript method in the OverlayPanelRender to transmit an additional property. You could access it then with _self.cfg.delay. Notice though that youll have to replace the component model OverlayPanel too providing it with an extra attribute.

like image 94
Sebastian Hoffmann Avatar answered Nov 26 '22 05:11

Sebastian Hoffmann


At the same time I thank you for this brilliant solution I take the opportunity to update it for Primefaces 5.2. In our application the code broke after that upgrade.

Follows the updated code for 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();
        }
    });
};

I also added an extra feature which allows the user to move the mouse over the overlay without hiding it. It should hide when you move the mouse out of it then which I accomplished through:

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

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

Hope you enjoy!

like image 32
user3088172 Avatar answered Nov 26 '22 06:11

user3088172