<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Creating a simple Tooltip widget</title>
<style type="text/css">
/*margin and padding on body element
can introduce errors in determining
element position and are not recommended;
we turn them off as a foundation for YUI
CSS treatments. */
body {
margin:0;
padding:0;
}
</style>
<link type="text/css" rel="stylesheet" href="../../build/cssfonts/fonts-min.css" />
<script type="text/javascript" src="../../build/yui/yui-min.js"></script>
<!--begin custom header content for this example-->
<style type="text/css">
.yui-tooltip {
position:absolute;
}
.yui-tooltip-content {
color: #000;
padding: 2px 5px;
border-color: #D4C237 #A6982B #A6982B #A6982B;
border-width: 1px;
border-style: solid;
background-color: #FFEE69;
}
.yui-tooltip-hidden {
visibility:hidden;
}
div.yui-hastooltip {
border:1px solid #243356;
background-color:#406ED9;
color:#ffffff;
width:25em;
margin:20px 0px;
padding:5px;
cursor:default;
}
div.yui-hastooltip span {
font-style:italic;
font-weight:bold;
color:#ABCEFF;
}
.yui-tooltip-content strong {
font-weight:bold;
}
</style>
<!--end custom header content for this example-->
</head>
<body class=" yui-skin-sam">
<h1>Creating a simple Tooltip widget</h1>
<div class="exampleIntro">
This is an advanced example, in which we create a Tooltip widget, by extending the base <code>Widget</code> class, and adding <code>WidgetStack</code> and <code>WidgetPosition</code> extensions, through <code>Base.build</code>.
</div>
<!--BEGIN SOURCE CODE FOR EXAMPLE =============================== -->
<div id="delegate">
<div class="yui-hastooltip" title="Tooltip 1" id="tt1">Tooltip One <span>(content from title)</span></div>
<div class="yui-hastooltip" title="Tooltip 2" id="tt2">Tooltip Two <span>(content set in event listener)</span></div>
<div class="yui-hastooltip" title="Tooltip 3" id="tt3">Tooltip Three <span>(content from lookup)</span></div>
<div class="yui-hastooltip" title="Tooltip 4" id="tt4">Tooltip Four <span>(content from title)</span></div>
<label><input type="checkbox" id="prevent" /> Prevent Tooltip Four</label>
</div>
<script type="text/javascript">
YUI({base:"../../build/", timeout: 10000}).use("widget", "widget-position", "widget-stack", function(Y) {
var Lang = Y.Lang,
Node = Y.Node;
/* Tooltip constructor */
function Tooltip(config) {
Tooltip.superclass.constructor.apply(this, arguments);
}
/*
Required NAME static field, used to identify the Widget class and
used as an event prefix, to generate class names etc. (set to the
class name in camel case).
*/
Tooltip.NAME = "tooltip";
/* Static constants */
Tooltip.OFFSET_X = 15;
Tooltip.OFFSET_Y = 15;
Tooltip.OFFSCREEN_X = -10000;
Tooltip.OFFSCREEN_Y = -10000;
/* Default Tooltip Attributes */
Tooltip.ATTRS = {
/*
* The tooltip content. This can either be a fixed content value,
* or a map of id-to-values, designed to be used when a single
* tooltip is mapped to multiple trigger elements.
*/
content : {
value: null
},
/*
* The set of nodes to bind to the tooltip instance. Can be a string,
* or a node instance.
*/
triggerNodes : {
value: null,
setter: function(val) {
if (val && Lang.isString(val)) {
val = Node.all(val);
}
return val;
}
},
/*
* The delegate node to which event listeners should be attached.
* This node should be an ancestor of all trigger nodes bound
* to the instance. By default the document is used.
*/
delegate : {
value: null,
setter: function(val) {
return Y.one(val) || Y.one("document");
}
},
/*
* The time to wait, after the mouse enters the trigger node,
* to display the tooltip
*/
showDelay : {
value:250
},
/*
* The time to wait, after the mouse leaves the trigger node,
* to hide the tooltip
*/
hideDelay : {
value:10
},
/*
* The time to wait, after the tooltip is first displayed for
* a trigger node, to hide it, if the mouse has not left the
* trigger node
*/
autoHideDelay : {
value:2000
},
/*
* Override the default visibility set by the widget base class
*/
visible : {
value:false
},
/*
* Override the default XY value set by the widget base class,
* to position the tooltip offscreen
*/
xy: {
value:[Tooltip.OFFSCREEN_X, Tooltip.OFFSCREEN_Y]
}
};
/* Extend the base Widget class */
Y.extend(Tooltip, Y.Widget, {
/*
* Initialization Code: Sets up privately used state
* properties, and publishes the events Tooltip introduces
*/
initializer : function(config) {
this._triggerClassName = this.getClassName("trigger");
// Currently bound trigger node information
this._currTrigger = {
node: null,
title: null,
mouseX: Tooltip.OFFSCREEN_X,
mouseY: Tooltip.OFFSCREEN_Y
};
// Event handles - mouse over is set on the delegate
// element, mousemove and mouseout are set on the trigger node
this._eventHandles = {
delegate: null,
trigger: {
mouseMove : null,
mouseOut: null
}
};
// Show/hide timers
this._timers = {
show: null,
hide: null
};
// Publish events introduced by Tooltip. Note the triggerEnter event is preventable,
// with the default behavior defined in the _defTriggerEnterFn method
this.publish("triggerEnter", {defaultFn: this._defTriggerEnterFn, preventable:true});
this.publish("triggerLeave", {preventable:false});
},
/*
* Destruction Code: Clears event handles, timers,
* and current trigger information
*/
destructor : function() {
this._clearCurrentTrigger();
this._clearTimers();
this._clearHandles();
},
/*
* bindUI is used to bind attribute change and dom event
* listeners
*/
bindUI : function() {
this.after("delegateChange", this._afterSetDelegate);
this.after("nodesChange", this._afterSetNodes);
this._bindDelegate();
},
/*
* syncUI is used to update the rendered DOM, based on the current
* Tooltip state
*/
syncUI : function() {
this._uiSetNodes(this.get("triggerNodes"));
},
/*
* Public method, which can be used by triggerEvent event listeners
* to set the content of the tooltip for the current trigger node
*/
setTriggerContent : function(content) {
var contentBox = this.get("contentBox");
contentBox.set("innerHTML", "");
if (content) {
if (content instanceof Node) {
for (var i = 0, l = content.size(); i < l; ++i) {
contentBox.appendChild(content.item(i));
}
} else if (Lang.isString(content)) {
contentBox.set("innerHTML", content);
}
}
},
/*
* Gets the closest ancestor of the given node,
* which is a tooltip trigger node
*/
getParentTrigger : function(node) {
var cn = this._triggerClassName;
return (node.hasClass(cn)) ? node : node.ancestor(function(node) {return node.hasClass(cn)});
},
/*
* Default attribute change listener for
* the triggerNodes attribute
*/
_afterSetNodes : function(e) {
this._uiSetNodes(e.newVal);
},
/*
* Default attribute change listener for
* the delegate attribute
*/
_afterSetDelegate : function(e) {
this._bindDelegate(e.newVal);
},
/*
* Updates the rendered DOM to reflect the
* set of trigger nodes passed in
*/
_uiSetNodes : function(nodes) {
if (this._triggerNodes) {
this._triggerNodes.removeClass(this._triggerClassName);
}
if (nodes) {
this._triggerNodes = nodes;
this._triggerNodes.addClass(this._triggerClassName);
}
},
/*
* Attaches the default mouseover DOM listener to the
* current delegate node
*/
_bindDelegate : function() {
var eventHandles = this._eventHandles;
if (eventHandles.delegate) {
eventHandles.delegate.detach();
eventHandles.delegate = null;
}
eventHandles.delegate = Y.on("mouseover", Y.bind(this._onDelegateMouseOver, this), this.get("delegate"));
},
/*
* Default mouse over DOM event listener.
*
* Delegates to the _enterTrigger method,
* if the mouseover enters a trigger node.
*/
_onDelegateMouseOver : function(e) {
var node = this.getParentTrigger(e.target);
if (node && (!this._currTrigger.node || !node.compareTo(this._currTrigger.node))) {
this._enterTrigger(node, e.pageX, e.pageY);
}
},
/*
* Default mouse out DOM event listener
*
* Delegates to _leaveTrigger if the mouseout
* leaves the current trigger node
*/
_onNodeMouseOut : function(e) {
var to = e.relatedTarget;
var trigger = e.currentTarget;
if (!trigger.contains(to)) {
this._leaveTrigger(trigger);
}
},
/*
* Default mouse move DOM event listener
*/
_onNodeMouseMove : function(e) {
this._overTrigger(e.pageX, e.pageY);
},
/*
* Default handler invoked when the mouse enters
* a trigger node. Fires the triggerEnter
* event which can be prevented by listeners to
* show the tooltip from being displayed.
*/
_enterTrigger : function(node, x, y) {
this._setCurrentTrigger(node, x, y);
this.fire("triggerEnter", {node:node, pageX:x, pageY:y});
},
/*
* Default handler for the triggerEvent event,
* which will setup the timer to display the tooltip,
* if the default handler has not been prevented.
*/
_defTriggerEnterFn : function(e) {
var node = e.node;
if (!this.get("disabled")) {
this._clearTimers();
var delay = (this.get("visible")) ? 0 : this.get("showDelay");
this._timers.show = Y.later(delay, this, this._showTooltip, [node]);
}
},
/*
* Default handler invoked when the mouse leaves
* the current trigger node. Fires the triggerLeave
* event and sets up the hide timer
*/
_leaveTrigger : function(node) {
this.fire("triggerLeave");
this._clearCurrentTrigger();
this._clearTimers();
this._timers.hide = Y.later(this.get("hideDelay"), this, this._hideTooltip);
},
/*
* Default handler invoked for mousemove events
* on the trigger node. Stores the current mouse
* x, y positions
*/
_overTrigger : function(x, y) {
this._currTrigger.mouseX = x;
this._currTrigger.mouseY = y;
},
/*
* Shows the tooltip, after moving it to the current mouse
* position.
*/
_showTooltip : function(node) {
var x = this._currTrigger.mouseX;
var y = this._currTrigger.mouseY;
this.move(x + Tooltip.OFFSET_X, y + Tooltip.OFFSET_Y);
this.show();
this._clearTimers();
this._timers.hide = Y.later(this.get("autoHideDelay"), this, this._hideTooltip);
},
/*
* Hides the tooltip, after clearing existing timers.
*/
_hideTooltip : function() {
this._clearTimers();
this.hide();
},
/*
* Set the rendered content of the tooltip for the current
* trigger, based on (in order of precedence):
*
* a). The string/node content attribute value
* b). From the content lookup map if it is set, or
* c). From the title attribute if set.
*/
_setTriggerContent : function(node) {
var content = this.get("content");
if (content && !(content instanceof Node || Lang.isString(content))) {
content = content[node.get("id")] || node.getAttribute("title");
}
this.setTriggerContent(content);
},
/*
* Set the currently bound trigger node information, clearing
* out the title attribute if set and setting up mousemove/out
* listeners.
*/
_setCurrentTrigger : function(node, x, y) {
var currTrigger = this._currTrigger,
triggerHandles = this._eventHandles.trigger;
this._setTriggerContent(node);
triggerHandles.mouseMove = Y.on("mousemove", Y.bind(this._onNodeMouseMove, this), node);
triggerHandles.mouseOut = Y.on("mouseout", Y.bind(this._onNodeMouseOut, this), node);
var title = node.getAttribute("title");
node.setAttribute("title", "");
currTrigger.mouseX = x;
currTrigger.mouseY = y;
currTrigger.node = node;
currTrigger.title = title;
},
/*
* Clear out the current trigger state, restoring
* the title attribute on the trigger node,
* if it was originally set.
*/
_clearCurrentTrigger : function() {
var currTrigger = this._currTrigger,
triggerHandles = this._eventHandles.trigger;
if (currTrigger.node) {
var node = currTrigger.node;
var title = currTrigger.title || "";
currTrigger.node = null;
currTrigger.title = "";
triggerHandles.mouseMove.detach();
triggerHandles.mouseOut.detach();
triggerHandles.mouseMove = null;
triggerHandles.mouseOut = null;
node.setAttribute("title", title);
}
},
/*
* Cancel any existing show/hide timers
*/
_clearTimers : function() {
var timers = this._timers;
if (timers.hide) {
timers.hide.cancel();
timers.hide = null;
}
if (timers.show) {
timers.show.cancel();
timers.show = null;
}
},
/*
* Detach any stored event handles
*/
_clearHandles : function() {
var eventHandles = this._eventHandles;
if (eventHandles.delegate) {
this._eventHandles.delegate.detach();
}
if (eventHandles.trigger.mouseOut) {
eventHandles.trigger.mouseOut.detach();
}
if (eventHandles.trigger.mouseMove) {
eventHandles.trigger.mouseMove.detach();
}
}
});
// dynamic:false = Modify the existing Tooltip class
Tooltip = Y.Base.build(Tooltip.NAME, Tooltip, [Y.WidgetPosition, Y.WidgetStack], {dynamic:false});
var tt = new Tooltip({
triggerNodes:".yui-hastooltip",
delegate: "#delegate",
content: {
tt3: "Tooltip 3 (from lookup)"
},
shim:false,
zIndex:2
});
tt.render();
tt.on("triggerEnter", function(e) {
var node = e.node;
if (node && node.get("id") == "tt2") {
this.setTriggerContent("Tooltip 2 (from triggerEvent)");
}
});
var prevent = Y.one("#prevent");
tt.on("triggerEnter", function(e) {
var node = e.node;
if (prevent.get("checked")) {
if (node && node.get("id") == "tt4") {
e.preventDefault();
}
}
});
});
</script>
<!--END SOURCE CODE FOR EXAMPLE =============================== -->
</body>
</html>