YUI3 Tooltip module

Just quickly posting this bit of code that I’ve written to have a play with YUI3. There’s definitely room for improvement (e.g. making Tooltip inherit from the Overlay widget) and I haven’t tested the code thoroughly yet. But for a first try with a library, I figured it was nice enough to get the expected result:

YUI.add('tooltip', function (Y) {
	var Tip = function (config) {
		Tip.superclass.constructor.apply(this, arguments);
	};
	
	Tip.ATTRS = {
		offsetX: {
			value: 10,
			setter: function (val) {
				return val;
			}
		},
		offsetY: {
			value: 10,
			setter: function (val) {
				return val;
			}
		},
		showDelay: {
			value: 0.5,
			setter: function (val) {
				return val*1000;
			},
			validator: function (val) {
				return Y.Lang.isNumber(val) && (val >= 0);
			}
		},
		hideDelay: {
			value: 1.5,
			setter: function (val) {
				return val*1000;
			},
			validator: function (val) {
				return Y.Lang.isNumber(val) && (val >= 0);
			}
		}
	};
	
	Tip.HTML_PARSER = {
		header: function (srcNode) {
			var h = srcNode.getAttribute("title").match(/header=[([^]]*)]/);
			if (h !== null && h.length > 1) {
				return h[1];
			}
			return "";
		},
		body: function (srcNode) {
			var b = srcNode.getAttribute("title").match(/body=[([^]]*)]/);
			if (b !== null && b.length > 1) {
				return b[1];
			}
			return "";
		}
	};
	
	Tip.NAME = "tooltip";
	
	Y.extend(Tip, Y.Widget, {
		_srcNode: null,
		_tipNode: null,
		_config: null,
		_timers: null,
		initializer: function (config) {
			this._config = config;
			this._srcNode = config.srcNode;
			this._createTipNode(config.header, config.body);
			this._timers = {
				show: null,
				hide: null
			};
			Y.on("mouseenter", Y.bind(this._onMouseEnter, this), this._srcNode);
			Y.on("mouseleave", Y.bind(this._onMouseLeave, this), this._srcNode);
		},
		destructor: function () {
			this._onMouseEnter.detach();
			this._onMouseLeave.detach();
			this._tipNode.remove();
		},
		_createTipNode: function (header, body) {
			this._tipNode = new Y.Overlay({
				headerContent: header,
				bodyContent: body,
				visible: false,
				width:"280px",
				constrain: true
			});
			this._srcNode.setAttribute("title", "");
			this._tipNode.render();
		},
		_onMouseEnter: function (e) {
			if (Y.BOL.activeTooltip !== null) {
				Y.BOL.activeTooltip.hide();
				Y.BOL.activeTooltip = null;
			}
			if (this._timers.hide !== null) {
				this._timers.hide = clearTimeout(this._timers.hide);
			}
			this._timers.show = setTimeout(Y.bind(function () {
				this._tipNode.set("xy", [e.pageX+this.get("offsetX"), e.pageY+this.get("offsetY")]);
				this._tipNode.show();
				Y.BOL.activeTooltip = this;
			}, this), this.get("showDelay"));
		},
		_onMouseLeave: function (e) {
			if (this._timers.show !== null) {
				this._timers.show = clearTimeout(this._timers.show);
			}
			this._timers.hide = setTimeout(Y.bind(function () {
				this.hide();
				Y.BOL.activeTooltip = null;
			}, this), this.get("hideDelay"));
		},
		hide: function () {
			if (this._timers.show !== null) {
				this._timers.show = clearTimeout(this._timers.show);
			}
			this._tipNode.hide();
		}
	});
	
	Y.namespace('BOL');
	Y.BOL.Tooltip = Tip;
	Y.BOL.activeTooltip = null;
},
'1.0', 
{
	requires: ['event-mouseenter', 'overlay']
});

To use it, the HTML needs to look like this:

<div id="testDiv" class="tooltip_trigger" title="header=[Verzendkosten per bestelling] body=[Nederland: &amp;euro; 1,95&lt;br&gt;Belgi&amp;euml;: &amp;euro; 0,00&lt;br&gt;Overige Europa, Noord- en Midden-Amerika, Cara&amp;iuml;bisch gebied, Azi&amp;euml;, Australi&amp;euml;, Nieuw-Zeeland: van &amp;euro; 3,45 tot &amp;euro; 19,80&lt;br&gt;&lt;br&gt;&lt;strong&gt;Let op!&lt;/strong&gt; Software, pc-accessoires, elektronica en telefonie worden niet in het buitenland bezorgd.&lt;br&gt;&lt;br&gt;]">Test</div>

And finally, to call it:

YUI().use('tooltip', function (Y) {
	Y.all(".tooltip_trigger").each(function (trgNode) {
		var tip = new Y.BOL.Tooltip({
			srcNode: trgNode
		});
	})
})