HoverPopup - a JQuery UI Plugin
Fiddling around with JQuery, JavaScript and assorted web development goodies in my spare time, I finally have a JQuery UI plugin I can call my own. This one mimics the behaviour you can find in the Gmail chat window. When you hover over a user, you'll get a panel that has additional details and actions for that context.
You can try out the plug-in here which is an easier way to understand what it does.
It wasn't all that hard and while I was mucking my application code with this UI logic, I figured I would write it as a JQueryUI plugin so it is re-usable and has the added advantage of keeping my code clean. It turned out to be a very fun and satisfying experience. Many thanks to this article for helping me understand what needed to be done - http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/
Disclaimer: My first JQueryUI plug-in and not too much after having learnt JavaScript, so use this at your own risk - I expect that an expert in either will be able to provide me good feedback on how things can be improved or written much better.
Here's the code for the plug-in. To use JQueryUI you need to follow the guidelines here. Thanks to the wonderful http://www.tohtml.com for syntax highlighting.
/*
* $(selector).hoverpopup({
leftMargin: [optional] - specify how much to the left of the trigger element the hover popup should appear
getTriggers: function that should return a list of elements to base the popup trigger on.
getPopupContents: function that is called to populate the popup
});
*
*/
var HoverPopup = {
options: {
leftMargin: 3,
},
_init: function() {
var _this = this;
var elem = this.element;
var triggers = this.options.getTriggers(elem);
_this.options.popup = $('#hover-popup-1');
if (_this.options.popup.length == 0) {
$('body').append("<div id='hover-popup-1' class='hover-popup'></div>");
_this.options.popup = $('#hover-popup-1');
_this.options.popup.hover(function(evt) {
clearTimeout(_this.options.t);
}, function(evt) {
});
_this.options.popup.hide();
$('html').click(_this.options.clickHandler = function(evt) {
if ($('#hover-popup-1:visible').length > 0) {
var o = _this.options.popup.offset();
var h = _this.options.popup.height();
var w = _this.options.popup.width();
if ((evt.pageX >= o.left && evt.pageX < o.left + w) &&
(evt.pageY >= o.top && evt.pageY < o.top + h)) {
// do nothing
} else {
_this.options.popup.fadeOut(_this.options.hideCallback);
}
}
});
}
for (var i = 0; i < triggers.length; i++) {
$(triggers[i]).hover(function(evt) {
_this.options.enter = setTimeout(function() {
var src = evt.currentTarget;
_this.options.popup.empty();
_this.options.popup.append(_this.options.getPopupContents(src));
clearTimeout(_this.options.t);
_this.options.popup.fadeIn(_this.options.showCallback);
_this.options.popup.offset(_this._calculateOffset(src));
}, 200);
}, function(evt) {
clearTimeout(_this.options.enter);
_this.options.t = setTimeout(function() {
_this.options.popup.fadeOut(_this.options.hideCallback);
}, 300);
});
}
},
destroy: function() {
$.Widget.prototype.destroy.apply(this, arguments);
_this.options.popup.unbind();
$('body').remove(_this.options.popup);
$('html').unbind(_this.options.clickHandler);
},
_calculateOffset: function(src) {
var w = this.options.popup.width();
var h = this.options.popup.height();
var jsrc = $(src);
var off = jsrc.offset();
var l = off.left - w - this.options.leftMargin;
if (l < 0) {
l = off.left + jsrc.width() + this.options.leftMargin;
}
var t = off.top;
var pageH = $(window).height();
if (t + h > pageH) {
t -= (t + h - pageH - 5);
}
return { "top" : t, "left" : l};
},
}
$.widget("ui.hoverpopup", HoverPopup);
The CSS code follows - which is not much really. You can style the contents of the popup which is your UI code. I suppose I could add this to the component itself and reduce the number of files.
#hover-popup-1 {
display: block;
position: absolute;
z-index: 10000;
}
Usage:
Apply hoverpopup on the jquery object - an instance will get instantiated for each element in the jquery object. The "getTriggers" function is called which gets the context element to work with. This function is expected to return a jquery object that has all the triggers in it. When the mouse hovers over one of the triggers, the popup is shown and its contents are populated by calling "getPopupContents" which can return anything that works in the JQuery "append" function.
leftMargin just controls where the popup is located - maybe this should be in a function of its own
Here's an example -
$("#div1").hoverpopup({
getTriggers: function(elem) {
return $(elem).find('.hp-test-tr');
},
getPopupContents: function(src) {
return '<div>' + $(src).find('.data').html() + '<div>';
},
leftMargin: 10,
});
Some more details
The reason I added in getTriggers, which is probably a deviation from the JQuery way of doing this - i.e. the pattern $(selector).hoverpopup would normally apply on the selector, was because I felt that instantiating a HoverPopup object for each trigger was excessive. Take a look at the demo page source to get an idea of how it was used.
Let me know what you think and I am open to suggestions on improvements and feedback on the code.