|
1 |
|
2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
|
3 <html> |
|
4 <head> |
|
5 <meta http-equiv="content-type" content="text/html; charset=utf-8"> |
|
6 <title>Creating a simple Tooltip widget</title> |
|
7 |
|
8 <style type="text/css"> |
|
9 /*margin and padding on body element |
|
10 can introduce errors in determining |
|
11 element position and are not recommended; |
|
12 we turn them off as a foundation for YUI |
|
13 CSS treatments. */ |
|
14 body { |
|
15 margin:0; |
|
16 padding:0; |
|
17 } |
|
18 </style> |
|
19 |
|
20 <link type="text/css" rel="stylesheet" href="../../build/cssfonts/fonts-min.css" /> |
|
21 <script type="text/javascript" src="../../build/yui/yui-min.js"></script> |
|
22 |
|
23 |
|
24 <!--begin custom header content for this example--> |
|
25 <style type="text/css"> |
|
26 .yui-tooltip { |
|
27 position:absolute; |
|
28 } |
|
29 |
|
30 .yui-tooltip-content { |
|
31 color: #000; |
|
32 padding: 2px 5px; |
|
33 border-color: #D4C237 #A6982B #A6982B #A6982B; |
|
34 border-width: 1px; |
|
35 border-style: solid; |
|
36 background-color: #FFEE69; |
|
37 } |
|
38 |
|
39 .yui-tooltip-hidden { |
|
40 visibility:hidden; |
|
41 } |
|
42 |
|
43 div.yui-hastooltip { |
|
44 border:1px solid #243356; |
|
45 background-color:#406ED9; |
|
46 color:#ffffff; |
|
47 width:25em; |
|
48 margin:20px 0px; |
|
49 padding:5px; |
|
50 cursor:default; |
|
51 } |
|
52 |
|
53 div.yui-hastooltip span { |
|
54 font-style:italic; |
|
55 font-weight:bold; |
|
56 color:#ABCEFF; |
|
57 } |
|
58 |
|
59 .yui-tooltip-content strong { |
|
60 font-weight:bold; |
|
61 } |
|
62 </style> |
|
63 |
|
64 <!--end custom header content for this example--> |
|
65 |
|
66 </head> |
|
67 |
|
68 <body class=" yui-skin-sam"> |
|
69 |
|
70 <h1>Creating a simple Tooltip widget</h1> |
|
71 |
|
72 <div class="exampleIntro"> |
|
73 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>. |
|
74 </div> |
|
75 |
|
76 <!--BEGIN SOURCE CODE FOR EXAMPLE =============================== --> |
|
77 |
|
78 <div id="delegate"> |
|
79 <div class="yui-hastooltip" title="Tooltip 1" id="tt1">Tooltip One <span>(content from title)</span></div> |
|
80 <div class="yui-hastooltip" title="Tooltip 2" id="tt2">Tooltip Two <span>(content set in event listener)</span></div> |
|
81 <div class="yui-hastooltip" title="Tooltip 3" id="tt3">Tooltip Three <span>(content from lookup)</span></div> |
|
82 <div class="yui-hastooltip" title="Tooltip 4" id="tt4">Tooltip Four <span>(content from title)</span></div> |
|
83 <label><input type="checkbox" id="prevent" /> Prevent Tooltip Four</label> |
|
84 </div> |
|
85 |
|
86 <script type="text/javascript"> |
|
87 YUI({base:"../../build/", timeout: 10000}).use("widget", "widget-position", "widget-stack", function(Y) { |
|
88 var Lang = Y.Lang, |
|
89 Node = Y.Node; |
|
90 |
|
91 /* Tooltip constructor */ |
|
92 function Tooltip(config) { |
|
93 Tooltip.superclass.constructor.apply(this, arguments); |
|
94 } |
|
95 |
|
96 /* |
|
97 Required NAME static field, used to identify the Widget class and |
|
98 used as an event prefix, to generate class names etc. (set to the |
|
99 class name in camel case). |
|
100 */ |
|
101 Tooltip.NAME = "tooltip"; |
|
102 |
|
103 /* Static constants */ |
|
104 Tooltip.OFFSET_X = 15; |
|
105 Tooltip.OFFSET_Y = 15; |
|
106 Tooltip.OFFSCREEN_X = -10000; |
|
107 Tooltip.OFFSCREEN_Y = -10000; |
|
108 |
|
109 /* Default Tooltip Attributes */ |
|
110 Tooltip.ATTRS = { |
|
111 |
|
112 /* |
|
113 * The tooltip content. This can either be a fixed content value, |
|
114 * or a map of id-to-values, designed to be used when a single |
|
115 * tooltip is mapped to multiple trigger elements. |
|
116 */ |
|
117 content : { |
|
118 value: null |
|
119 }, |
|
120 |
|
121 /* |
|
122 * The set of nodes to bind to the tooltip instance. Can be a string, |
|
123 * or a node instance. |
|
124 */ |
|
125 triggerNodes : { |
|
126 value: null, |
|
127 setter: function(val) { |
|
128 if (val && Lang.isString(val)) { |
|
129 val = Node.all(val); |
|
130 } |
|
131 return val; |
|
132 } |
|
133 }, |
|
134 |
|
135 /* |
|
136 * The delegate node to which event listeners should be attached. |
|
137 * This node should be an ancestor of all trigger nodes bound |
|
138 * to the instance. By default the document is used. |
|
139 */ |
|
140 delegate : { |
|
141 value: null, |
|
142 setter: function(val) { |
|
143 return Y.one(val) || Y.one("document"); |
|
144 } |
|
145 }, |
|
146 |
|
147 /* |
|
148 * The time to wait, after the mouse enters the trigger node, |
|
149 * to display the tooltip |
|
150 */ |
|
151 showDelay : { |
|
152 value:250 |
|
153 }, |
|
154 |
|
155 /* |
|
156 * The time to wait, after the mouse leaves the trigger node, |
|
157 * to hide the tooltip |
|
158 */ |
|
159 hideDelay : { |
|
160 value:10 |
|
161 }, |
|
162 |
|
163 /* |
|
164 * The time to wait, after the tooltip is first displayed for |
|
165 * a trigger node, to hide it, if the mouse has not left the |
|
166 * trigger node |
|
167 */ |
|
168 autoHideDelay : { |
|
169 value:2000 |
|
170 }, |
|
171 |
|
172 /* |
|
173 * Override the default visibility set by the widget base class |
|
174 */ |
|
175 visible : { |
|
176 value:false |
|
177 }, |
|
178 |
|
179 /* |
|
180 * Override the default XY value set by the widget base class, |
|
181 * to position the tooltip offscreen |
|
182 */ |
|
183 xy: { |
|
184 value:[Tooltip.OFFSCREEN_X, Tooltip.OFFSCREEN_Y] |
|
185 } |
|
186 }; |
|
187 |
|
188 /* Extend the base Widget class */ |
|
189 Y.extend(Tooltip, Y.Widget, { |
|
190 |
|
191 /* |
|
192 * Initialization Code: Sets up privately used state |
|
193 * properties, and publishes the events Tooltip introduces |
|
194 */ |
|
195 initializer : function(config) { |
|
196 |
|
197 this._triggerClassName = this.getClassName("trigger"); |
|
198 |
|
199 // Currently bound trigger node information |
|
200 this._currTrigger = { |
|
201 node: null, |
|
202 title: null, |
|
203 mouseX: Tooltip.OFFSCREEN_X, |
|
204 mouseY: Tooltip.OFFSCREEN_Y |
|
205 }; |
|
206 |
|
207 // Event handles - mouse over is set on the delegate |
|
208 // element, mousemove and mouseout are set on the trigger node |
|
209 this._eventHandles = { |
|
210 delegate: null, |
|
211 trigger: { |
|
212 mouseMove : null, |
|
213 mouseOut: null |
|
214 } |
|
215 }; |
|
216 |
|
217 // Show/hide timers |
|
218 this._timers = { |
|
219 show: null, |
|
220 hide: null |
|
221 }; |
|
222 |
|
223 // Publish events introduced by Tooltip. Note the triggerEnter event is preventable, |
|
224 // with the default behavior defined in the _defTriggerEnterFn method |
|
225 this.publish("triggerEnter", {defaultFn: this._defTriggerEnterFn, preventable:true}); |
|
226 this.publish("triggerLeave", {preventable:false}); |
|
227 }, |
|
228 |
|
229 /* |
|
230 * Destruction Code: Clears event handles, timers, |
|
231 * and current trigger information |
|
232 */ |
|
233 destructor : function() { |
|
234 this._clearCurrentTrigger(); |
|
235 this._clearTimers(); |
|
236 this._clearHandles(); |
|
237 }, |
|
238 |
|
239 /* |
|
240 * bindUI is used to bind attribute change and dom event |
|
241 * listeners |
|
242 */ |
|
243 bindUI : function() { |
|
244 this.after("delegateChange", this._afterSetDelegate); |
|
245 this.after("nodesChange", this._afterSetNodes); |
|
246 |
|
247 this._bindDelegate(); |
|
248 }, |
|
249 |
|
250 /* |
|
251 * syncUI is used to update the rendered DOM, based on the current |
|
252 * Tooltip state |
|
253 */ |
|
254 syncUI : function() { |
|
255 this._uiSetNodes(this.get("triggerNodes")); |
|
256 }, |
|
257 |
|
258 /* |
|
259 * Public method, which can be used by triggerEvent event listeners |
|
260 * to set the content of the tooltip for the current trigger node |
|
261 */ |
|
262 setTriggerContent : function(content) { |
|
263 var contentBox = this.get("contentBox"); |
|
264 contentBox.set("innerHTML", ""); |
|
265 |
|
266 if (content) { |
|
267 if (content instanceof Node) { |
|
268 for (var i = 0, l = content.size(); i < l; ++i) { |
|
269 contentBox.appendChild(content.item(i)); |
|
270 } |
|
271 } else if (Lang.isString(content)) { |
|
272 contentBox.set("innerHTML", content); |
|
273 } |
|
274 } |
|
275 }, |
|
276 |
|
277 /* |
|
278 * Gets the closest ancestor of the given node, |
|
279 * which is a tooltip trigger node |
|
280 */ |
|
281 getParentTrigger : function(node) { |
|
282 var cn = this._triggerClassName; |
|
283 return (node.hasClass(cn)) ? node : node.ancestor(function(node) {return node.hasClass(cn)}); |
|
284 }, |
|
285 |
|
286 /* |
|
287 * Default attribute change listener for |
|
288 * the triggerNodes attribute |
|
289 */ |
|
290 _afterSetNodes : function(e) { |
|
291 this._uiSetNodes(e.newVal); |
|
292 }, |
|
293 |
|
294 /* |
|
295 * Default attribute change listener for |
|
296 * the delegate attribute |
|
297 */ |
|
298 _afterSetDelegate : function(e) { |
|
299 this._bindDelegate(e.newVal); |
|
300 }, |
|
301 |
|
302 /* |
|
303 * Updates the rendered DOM to reflect the |
|
304 * set of trigger nodes passed in |
|
305 */ |
|
306 _uiSetNodes : function(nodes) { |
|
307 if (this._triggerNodes) { |
|
308 this._triggerNodes.removeClass(this._triggerClassName); |
|
309 } |
|
310 |
|
311 if (nodes) { |
|
312 this._triggerNodes = nodes; |
|
313 this._triggerNodes.addClass(this._triggerClassName); |
|
314 } |
|
315 }, |
|
316 |
|
317 /* |
|
318 * Attaches the default mouseover DOM listener to the |
|
319 * current delegate node |
|
320 */ |
|
321 _bindDelegate : function() { |
|
322 var eventHandles = this._eventHandles; |
|
323 |
|
324 if (eventHandles.delegate) { |
|
325 eventHandles.delegate.detach(); |
|
326 eventHandles.delegate = null; |
|
327 } |
|
328 eventHandles.delegate = Y.on("mouseover", Y.bind(this._onDelegateMouseOver, this), this.get("delegate")); |
|
329 }, |
|
330 |
|
331 /* |
|
332 * Default mouse over DOM event listener. |
|
333 * |
|
334 * Delegates to the _enterTrigger method, |
|
335 * if the mouseover enters a trigger node. |
|
336 */ |
|
337 _onDelegateMouseOver : function(e) { |
|
338 var node = this.getParentTrigger(e.target); |
|
339 if (node && (!this._currTrigger.node || !node.compareTo(this._currTrigger.node))) { |
|
340 this._enterTrigger(node, e.pageX, e.pageY); |
|
341 } |
|
342 }, |
|
343 |
|
344 /* |
|
345 * Default mouse out DOM event listener |
|
346 * |
|
347 * Delegates to _leaveTrigger if the mouseout |
|
348 * leaves the current trigger node |
|
349 */ |
|
350 _onNodeMouseOut : function(e) { |
|
351 var to = e.relatedTarget; |
|
352 var trigger = e.currentTarget; |
|
353 |
|
354 if (!trigger.contains(to)) { |
|
355 this._leaveTrigger(trigger); |
|
356 } |
|
357 }, |
|
358 |
|
359 /* |
|
360 * Default mouse move DOM event listener |
|
361 */ |
|
362 _onNodeMouseMove : function(e) { |
|
363 this._overTrigger(e.pageX, e.pageY); |
|
364 }, |
|
365 |
|
366 /* |
|
367 * Default handler invoked when the mouse enters |
|
368 * a trigger node. Fires the triggerEnter |
|
369 * event which can be prevented by listeners to |
|
370 * show the tooltip from being displayed. |
|
371 */ |
|
372 _enterTrigger : function(node, x, y) { |
|
373 this._setCurrentTrigger(node, x, y); |
|
374 this.fire("triggerEnter", {node:node, pageX:x, pageY:y}); |
|
375 }, |
|
376 |
|
377 /* |
|
378 * Default handler for the triggerEvent event, |
|
379 * which will setup the timer to display the tooltip, |
|
380 * if the default handler has not been prevented. |
|
381 */ |
|
382 _defTriggerEnterFn : function(e) { |
|
383 var node = e.node; |
|
384 if (!this.get("disabled")) { |
|
385 this._clearTimers(); |
|
386 var delay = (this.get("visible")) ? 0 : this.get("showDelay"); |
|
387 this._timers.show = Y.later(delay, this, this._showTooltip, [node]); |
|
388 } |
|
389 }, |
|
390 |
|
391 /* |
|
392 * Default handler invoked when the mouse leaves |
|
393 * the current trigger node. Fires the triggerLeave |
|
394 * event and sets up the hide timer |
|
395 */ |
|
396 _leaveTrigger : function(node) { |
|
397 this.fire("triggerLeave"); |
|
398 |
|
399 this._clearCurrentTrigger(); |
|
400 this._clearTimers(); |
|
401 |
|
402 this._timers.hide = Y.later(this.get("hideDelay"), this, this._hideTooltip); |
|
403 }, |
|
404 |
|
405 /* |
|
406 * Default handler invoked for mousemove events |
|
407 * on the trigger node. Stores the current mouse |
|
408 * x, y positions |
|
409 */ |
|
410 _overTrigger : function(x, y) { |
|
411 this._currTrigger.mouseX = x; |
|
412 this._currTrigger.mouseY = y; |
|
413 }, |
|
414 |
|
415 /* |
|
416 * Shows the tooltip, after moving it to the current mouse |
|
417 * position. |
|
418 */ |
|
419 _showTooltip : function(node) { |
|
420 var x = this._currTrigger.mouseX; |
|
421 var y = this._currTrigger.mouseY; |
|
422 |
|
423 this.move(x + Tooltip.OFFSET_X, y + Tooltip.OFFSET_Y); |
|
424 |
|
425 this.show(); |
|
426 this._clearTimers(); |
|
427 |
|
428 this._timers.hide = Y.later(this.get("autoHideDelay"), this, this._hideTooltip); |
|
429 }, |
|
430 |
|
431 /* |
|
432 * Hides the tooltip, after clearing existing timers. |
|
433 */ |
|
434 _hideTooltip : function() { |
|
435 this._clearTimers(); |
|
436 this.hide(); |
|
437 }, |
|
438 |
|
439 /* |
|
440 * Set the rendered content of the tooltip for the current |
|
441 * trigger, based on (in order of precedence): |
|
442 * |
|
443 * a). The string/node content attribute value |
|
444 * b). From the content lookup map if it is set, or |
|
445 * c). From the title attribute if set. |
|
446 */ |
|
447 _setTriggerContent : function(node) { |
|
448 var content = this.get("content"); |
|
449 if (content && !(content instanceof Node || Lang.isString(content))) { |
|
450 content = content[node.get("id")] || node.getAttribute("title"); |
|
451 } |
|
452 this.setTriggerContent(content); |
|
453 }, |
|
454 |
|
455 /* |
|
456 * Set the currently bound trigger node information, clearing |
|
457 * out the title attribute if set and setting up mousemove/out |
|
458 * listeners. |
|
459 */ |
|
460 _setCurrentTrigger : function(node, x, y) { |
|
461 |
|
462 var currTrigger = this._currTrigger, |
|
463 triggerHandles = this._eventHandles.trigger; |
|
464 |
|
465 this._setTriggerContent(node); |
|
466 |
|
467 triggerHandles.mouseMove = Y.on("mousemove", Y.bind(this._onNodeMouseMove, this), node); |
|
468 triggerHandles.mouseOut = Y.on("mouseout", Y.bind(this._onNodeMouseOut, this), node); |
|
469 |
|
470 var title = node.getAttribute("title"); |
|
471 node.setAttribute("title", ""); |
|
472 |
|
473 currTrigger.mouseX = x; |
|
474 currTrigger.mouseY = y; |
|
475 currTrigger.node = node; |
|
476 currTrigger.title = title; |
|
477 }, |
|
478 |
|
479 /* |
|
480 * Clear out the current trigger state, restoring |
|
481 * the title attribute on the trigger node, |
|
482 * if it was originally set. |
|
483 */ |
|
484 _clearCurrentTrigger : function() { |
|
485 |
|
486 var currTrigger = this._currTrigger, |
|
487 triggerHandles = this._eventHandles.trigger; |
|
488 |
|
489 if (currTrigger.node) { |
|
490 var node = currTrigger.node; |
|
491 var title = currTrigger.title || ""; |
|
492 |
|
493 currTrigger.node = null; |
|
494 currTrigger.title = ""; |
|
495 |
|
496 triggerHandles.mouseMove.detach(); |
|
497 triggerHandles.mouseOut.detach(); |
|
498 triggerHandles.mouseMove = null; |
|
499 triggerHandles.mouseOut = null; |
|
500 |
|
501 node.setAttribute("title", title); |
|
502 } |
|
503 }, |
|
504 |
|
505 /* |
|
506 * Cancel any existing show/hide timers |
|
507 */ |
|
508 _clearTimers : function() { |
|
509 var timers = this._timers; |
|
510 if (timers.hide) { |
|
511 timers.hide.cancel(); |
|
512 timers.hide = null; |
|
513 } |
|
514 if (timers.show) { |
|
515 timers.show.cancel(); |
|
516 timers.show = null; |
|
517 } |
|
518 }, |
|
519 |
|
520 /* |
|
521 * Detach any stored event handles |
|
522 */ |
|
523 _clearHandles : function() { |
|
524 var eventHandles = this._eventHandles; |
|
525 |
|
526 if (eventHandles.delegate) { |
|
527 this._eventHandles.delegate.detach(); |
|
528 } |
|
529 if (eventHandles.trigger.mouseOut) { |
|
530 eventHandles.trigger.mouseOut.detach(); |
|
531 } |
|
532 if (eventHandles.trigger.mouseMove) { |
|
533 eventHandles.trigger.mouseMove.detach(); |
|
534 } |
|
535 } |
|
536 }); |
|
537 |
|
538 // dynamic:false = Modify the existing Tooltip class |
|
539 Tooltip = Y.Base.build(Tooltip.NAME, Tooltip, [Y.WidgetPosition, Y.WidgetStack], {dynamic:false}); |
|
540 |
|
541 var tt = new Tooltip({ |
|
542 triggerNodes:".yui-hastooltip", |
|
543 delegate: "#delegate", |
|
544 content: { |
|
545 tt3: "Tooltip 3 (from lookup)" |
|
546 }, |
|
547 shim:false, |
|
548 zIndex:2 |
|
549 }); |
|
550 tt.render(); |
|
551 |
|
552 tt.on("triggerEnter", function(e) { |
|
553 var node = e.node; |
|
554 if (node && node.get("id") == "tt2") { |
|
555 this.setTriggerContent("Tooltip 2 (from triggerEvent)"); |
|
556 } |
|
557 }); |
|
558 |
|
559 var prevent = Y.one("#prevent"); |
|
560 tt.on("triggerEnter", function(e) { |
|
561 var node = e.node; |
|
562 if (prevent.get("checked")) { |
|
563 if (node && node.get("id") == "tt4") { |
|
564 e.preventDefault(); |
|
565 } |
|
566 } |
|
567 }); |
|
568 }); |
|
569 </script> |
|
570 |
|
571 <!--END SOURCE CODE FOR EXAMPLE =============================== --> |
|
572 |
|
573 </body> |
|
574 </html> |