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 -
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.