1/************************************************************************* 2 This code is from Dynamic Web Coding at dyn-web.com 3 Copyright 2003-2008 by Sharon Paine 4 See Terms of Use at www.dyn-web.com/business/terms.php 5 regarding conditions under which you may use this code. 6 This notice must be retained in the code as is! 7 8 version date: Aug 2008 9 requires: dw_event.js (april 2008 version) 10 and dw_viewport.js (march 2008 version) 11*************************************************************************/ 12 13var dw_Tooltip = { 14 offX: 12, 15 offY: 12, 16 showDelay: 100, 17 hideDelay: 100, 18 hoverDelay: 500, // for hover tip 19 tipID: "tipDiv", 20 actuatorClass: "showTip", 21 maxLoops: 2, // for actuator check (linked image, etc.) 22 activateOnfocus: true, 23 tip: null, shim:null, timer: 0, hoverTimer: 0, 24 active: false, actuator: null, resetFlag: false, restored: true, 25 on_show: function() {}, on_position: function() {}, on_hide: function() {}, 26 27 init: function() { 28 var _this = dw_Tooltip; 29 if ( document.createElement && document.body && typeof document.body.appendChild != "undefined" ) { 30 var el = document.createElement("div"); 31 el.id = _this.tipID; el.style.position = 'absolute'; 32 el.style.visibility = 'hidden'; el.style.zIndex = 10000; 33 el.style.lineHeight = "1.2"; 34 document.body.appendChild(el); 35 _this.tip = document.getElementById( _this.tipID); 36 _this.setDefaults(); 37 if ( _this.checkOverlaySupport() ) { _this.prepOverlay(); } 38 _this.setPosition(0, 0); 39 } 40 }, 41 42 setDefaults: function() { // called when props changed (resetFlag set) 43 if ( !this.defaultProps ) this.defaultProps = {}; 44 // prop name, type, default 45 var list = [ ['followMouse', 'boolean', true], ['sticky', 'boolean', false], ['klass', 'string', ''], 46 ['hoverable', 'boolean', false], ['duration', 'number', 0], 47 ['jumpAbove', 'boolean', true], ['jumpLeft', 'boolean', true], 48 ['Left', 'boolean', false], ['Above', 'boolean', false], 49 ['positionFn', 'function', this.positionRelEvent], 50 ['wrapFn', 'function', function(str) { return str; } ] ]; 51 52 for (var i=0; list[i]; i++) { 53 this[ list[i][0] ] = ( typeof this.defaultProps[ list[i][0] ] == list[i][1] )? 54 this.defaultProps[ list[i][0] ]: list[i][2]; 55 } 56 57 this.tip.className = this.klass; 58 this.coordinateOptions(); 59 }, 60 61 activate: function(e, tgt, msg, id) { 62 var _this = dw_Tooltip; if (!_this.tip) return; 63 _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); 64 if ( !_this.restored ) _this.handleRestore(); 65 _this.actuator = tgt; dw_Viewport.getAll(); 66 _this.getContent(e, tgt, msg, id); _this.restored = false; 67 if ( !_this.tip.innerHTML ) return; _this.active = true; 68 _this.handleOptions(e); _this.positionFn(e, tgt); _this.adjust(); 69 _this.timer = setTimeout(_this.show, _this.showDelay); 70 }, 71 72 getContent: function(e, tgt, msg, id) { 73 msg = msg || ''; 74 if (id && !msg) { 75 var obj = (id && this.content_vars && this.content_vars[id])? this.content_vars[id]: false; 76 if ( typeof obj == 'string' ) { 77 msg = obj; 78 } else if ( typeof obj == 'object' ) { 79 this.checkForProps( obj ); 80 if ( obj['content'] ) { 81 msg = obj['content']; 82 } else if ( obj['html_id'] ) { // id of page element 83 var el = document.getElementById( obj['html_id'] ); 84 if (el) msg = el.innerHTML; 85 } else { 86 msg = obj; // wrapFn will obtain props from obj 87 } 88 } 89 } 90 this.writeTip(msg); 91 }, 92 93 writeTip: function(msg, bReqFlag) { 94 if ( this.pendingReq && this.respRecd && !bReqFlag ) return;
95 msg = this.wrapFn(msg); this.tip.innerHTML = msg; 96 }, 97 98 positionRelEvent: function(e, tgt) { 99 var _this = dw_Tooltip; 100 if (typeof e == 'object') { // event 101 if ( e.type == 'mouseover' || e.type == 'mousemove' ) { 102 _this.evX = _this.getMouseEventX(e); 103 _this.evY = _this.getMouseEventY(e); 104 } else { // focus 105 var pos = dw_getPageOffsets( tgt ); 106 _this.evX = pos.x; 107 _this.evY = pos.y; 108 } 109 } 110 111 var coords = _this.calcPosCoords(e, tgt); 112 _this.setPosition(coords.x, coords.y); 113 }, 114 115 calcPosCoords: function(e, tgt) { 116 var x = this.evX; var y = this.evY; var xXd, yXd; 117 var maxX = this.getMaxX(); var maxY = this.getMaxY(); // tip width/height too 118 119 var tx = x + this.offX; 120 var altx = x - ( this.width + this.offX ); 121 var spL = x - dw_Viewport.scrollX > dw_Viewport.width/2; 122 123 if ( typeof e == 'object' && e.type && ( e.type == 'focus' || e.type == 'focusin' ) ) { 124 var tgtWidth = tgt.offsetWidth; 125 if ( tx + tgtWidth < maxX ) { 126 x = this.evX = x + tgtWidth; 127 tx += tgtWidth; 128 } else if (tx + 20 < maxX ) { 129 x = this.evX = x + 20; 130 tx += 20 131 } 132 y = this.evY = y + 10; 133 } 134 135 var ty = y + this.offY; 136 var alty = y - ( this.height + this.offY ); 137 var spA = y - dw_Viewport.scrollY > dw_Viewport.height/2; 138 139 if ( !this.Left && tx < maxX ) { 140 x = tx; 141 } else if ( ( this.Left && altx >= dw_Viewport.scrollX ) || 142 ( this.jumpLeft && tx >= maxX && altx >= dw_Viewport.scrollX ) ) { 143 x = altx; 144 } else if ( ( this.Left && altx < dw_Viewport.scrollX ) || 145 ( !this.Left && this.jumpLeft && altx < dw_Viewport.scrollX && spL ) ) { 146 x = dw_Viewport.scrollX; // place at left edge 147 xXd = 'Left'; // check later whether yXd too 148 } else if ( !this.Left && tx >= maxX && ( !this.jumpLeft || 149 ( this.jumpLeft && altx < dw_Viewport.scrollX && !spL ) ) ) { 150 x = maxX; xXd = 'Right'; 151 } 152 153 if ( !this.Above && ty < maxY ) { 154 y = ty; 155 } else if ( ( this.Above && alty >= dw_Viewport.scrollY ) || 156 ( this.jumpAbove && ty >= maxY && alty >= dw_Viewport.scrollY ) ) { 157 y = alty; 158 } else if ( ( this.Above && alty < dw_Viewport.scrollY ) || 159 ( !this.Above && this.jumpAbove && alty < dw_Viewport.scrollY && spA ) ) { 160 y = dw_Viewport.scrollY; // place at top 161 yXd = 'Above'; 162 } else if ( !this.Above && ty >= maxY && ( !this.jumpAbove || 163 ( this.jumpAbove && alty < dw_Viewport.scrollY && !spA ) ) ) { 164 y = maxY; yXd = 'Below'; 165 } 166 167 if ( xXd && yXd ) { // over link (will flicker) calc least distance to uncover 168 var dx = (xXd == 'Left')? dw_Viewport.scrollX - altx: tx - maxX; 169 var dy = (yXd == 'Above')? dw_Viewport.scrollY - alty: ty - maxY; 170 if ( dx <= dy ) { 171 x = (xXd == 'Left')? altx: tx; 172 } else { 173 y = (yXd == 'Above')? alty: ty; 174 } 175 } 176 return { x: x, y: y } 177 }, 178 179 adjust: function() { 180 var _this = dw_Tooltip; 181 var imgs = _this.tip.getElementsByTagName('img'); 182 var img = imgs.length? imgs[imgs.length - 1]: null; 183 checkComplete(); 184 185 function checkComplete() { 186 if ( !_this.active ) return; 187 _this.positionFn(); 188 if (img && !img.complete) { 189 setTimeout( checkComplete, 50); 190 } 191 } 192 }, 193 194 setPosition: function(x, y) { 195 this.tip.style.left = x + 'px'; 196 this.tip.style.top = y + 'px'; 197 this.setOverlay(); this.on_position(); 198 }, 199 200 show: function() { 201 var _this = dw_Tooltip; 202 _this.tip.style.visibility = 'visible'; 203 if ( _this.shim ) _this.shim.style.visibility = 'visible'; 204 _this.on_show(); 205 }, 206 207 deactivate: function(e) { 208 var _this = dw_Tooltip; if (!_this.tip || !_this.active || _this.sticky ) return; 209 e = e? e: window.event; 210 if (e.type && e.type == 'mouseout' && !dw_mouseleave(e, _this.actuator) ) return; 211 _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); 212 213 if ( _this.hoverable ) { // delayed call to hide (time to check if hovered over tip) 214 _this.hoverTimer = setTimeout( _this.hide, _this.hoverDelay ); 215 return; 216 } 217 if ( _this.duration ) { 218 _this.timer = setTimeout( _this.hide, _this.duration ); 219 return; 220 } 221 _this.timer = setTimeout( _this.hide, _this.hideDelay ); 222 }, 223 224 hide: function() { 225 var _this = dw_Tooltip; if (!_this.tip) return; 226 _this.tip.style.visibility = 'hidden'; 227 if ( _this.shim ) _this.shim.style.visibility = 'hidden'; 228 _this.handleRestore(); _this.on_hide(); 229 }, 230 231 handleOptions: function(e) { 232 this.coordinateOptions(); 233 if ( this.klass ) { this.tip.className = this.klass; } 234 if ( this.hoverable ) { 235 this.tip.onmouseout = dw_Tooltip.tipOutCheck; 236 this.tip.onmouseover = function() { dw_Tooltip.clearTimer('hoverTimer'); } 237 } 238 if ( this.followMouse && !this.hoverable && !(e.type == 'focus' || e.type == 'focusin') ) { 239 dw_Event.add(document, 'mousemove', this.positionRelEvent); 240 } 241 242 if ( this.sticky || this.duration ) { 243 dw_Event.add( document, "mouseup", dw_Tooltip.checkDocClick ); 244 } 245 }, 246 247 coordinateOptions: function() { 248 if ( this.sticky || this.hoverable || this.duration ) { this.followMouse = false; } 249 if ( this.sticky ) { this.hoverable = false; this.duration = 0; } 250 if ( this.hoverable ) { this.duration = 0; } 251 if ( this.positionFn != this.positionRelEvent ) this.followMouse = false; 252 }, 253 254 handleRestore: function() { 255 if ( this.followMouse ) { 256 dw_Event.remove(document, 'mousemove', this.positionRelEvent); 257 } 258 if ( this.sticky || this.duration ) { 259 dw_Event.remove( document, "mouseup", dw_Tooltip.checkDocClick, false ); 260 } 261 this.tip.onmouseover = this.tip.onmouseout = function() {} 262 263 if ( this.resetFlag ) this.setDefaults(); 264 this.writeTip(''); 265 266 this.active = false; this.actuator = null; 267 this.tip.style.width = ''; 268 this.restored = true; 269 }, 270 271 // first class name is actuatorClass, second class would point to content 272 getTipClass: function(cls) { 273 if (!cls) return ''; var c = ''; 274 var classes = cls.split(/\s+/); 275 if ( classes[0] == this.actuatorClass && classes[1] ) { 276 c = classes[1]; 277 } 278 return c;
278 // return second class name or '' 279 }, 280 281 checkForProps: function(obj) { 282 var list = ['jumpAbove', 'jumpLeft', 'Above', 'Left', 'sticky', 'duration', 283 'hoverable', 'followMouse', 'klass', 'positionFn', 'wrapFn']; 284 for (var i=0; list[i]; i++) { 285 if ( typeof obj[ list[i] ] != 'undefined' ) { 286 this[ list[i] ] = obj[ list[i] ]; 287 this.resetFlag = true; 288 } 289 } 290 }, 291 292 tipOutCheck: function(e) { // hover tip 293 var _this = dw_Tooltip; e = dw_Event.DOMit(e); 294 var tip = this; // assigned to onmouseover property of tip 295 if ( dw_mouseleave(e, tip) ) { 296 _this.timer = setTimeout( _this.hide, _this.hideDelay); 297 } 298 }, 299 300 checkEscKey: function(e) { // for sticky, duration, and onfocus activation 301 e = e? e: window.event; if ( e.keyCode == 27 ) dw_Tooltip.hide(); 302 }, 303 304 checkDocClick: function(e) { 305 if ( !dw_Tooltip.active ) return; 306 var tgt = dw_Event.getTarget(e); 307 // hide tooltip if you click anywhere in the document 308 // except on the tooltip, unless that click is on the tooltip's close box 309 var tip = document.getElementById(dw_Tooltip.tipID); 310 if ( tgt == tip || dw_contained(tgt, tip) ) { 311 if ( tgt.tagName && tgt.tagName.toLowerCase() == "img" ) tgt = tgt.parentNode; 312 if ( tgt.tagName.toLowerCase() != "a" || tgt.href.indexOf("dw_Tooltip.hide") != -1 ) return; 313 } 314 // slight delay to avoid crossing onfocus activation and doc click hide 315 dw_Tooltip.timer = setTimeout( dw_Tooltip.hide, 50); 316 }, 317 318 // check need for and support of iframe shim (for ie win and select lists) 319 checkOverlaySupport: function() { 320 if ( navigator.userAgent.indexOf("Windows") != -1 && 321 typeof document.body != "undefined" && 322 typeof document.body.insertAdjacentHTML != "undefined" && 323 !window.opera && navigator.appVersion.indexOf("MSIE 5.0") == -1 324 ) return true; 325 return false; 326 }, 327 328 prepOverlay: function() { 329 document.body.insertAdjacentHTML("beforeEnd", '<iframe id="tipShim" src="javascript: false" style="position:absolute; left:0; top:0; z-index:500; visibility:hidden" scrolling="no" frameborder="0"></iframe>'); 330 this.shim = document.getElementById('tipShim'); 331 if (this.shim && this.tip) { 332 this.shim.style.width = this.tip.offsetWidth + "px"; 333 this.shim.style.height = this.tip.offsetHeight + "px"; 334 } 335 }, 336 337 setOverlay: function() { // position and dimensions 338 if ( this.shim ) { 339 this.shim.style.left = this.tip.style.left; 340 this.shim.style.top = this.tip.style.top; 341 this.shim.style.width = this.tip.offsetWidth + "px"; 342 this.shim.style.height = this.tip.offsetHeight + "px"; 343 } 344 }, 345 346 clearTimer: function(timer) { 347 if ( dw_Tooltip[timer] ) { clearTimeout( dw_Tooltip[timer] ); dw_Tooltip[timer] = 0; } 348 }, 349 350 getWidth: function() { return this.width = this.tip.offsetWidth; }, 351 getHeight: function() { return this.height = this.tip.offsetHeight; }, 352 getMaxX: function() { return dw_Viewport.width + dw_Viewport.scrollX - this.getWidth() - 1; }, 353 getMaxY: function() { return dw_Viewport.height + dw_Viewport.scrollY - this.getHeight() - 1; }, 354 getMouseEventX: function(e) { return e.pageX? e.pageX: e.clientX + dw_Viewport.scrollX; }, 355 getMouseEventY: function(e) { return e.pageY? e.pageY: e.clientY + dw_Viewport.scrollY; } 356 357} 358 359// code for event delegation 360dw_Tooltip.initHandlers = function () { 361 dw_Event.add(document, 'mouseover', dw_Tooltip.checkActuatorMouseover); 362 dw_Event.add( document, "keydown", dw_Tooltip.checkEscKey, true ); // for sticky 363 dw_Event.add( window, 'blur', dw_Tooltip.deactivate, true ); 364 dw_Event.add( window, 'unload', dw_Tooltip.deactivate, true ); // firefox needs 365 366 // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 367 if ( dw_Tooltip.activateOnfocus ) { 368 document.onfocusin = dw_Tooltip.checkActuatorFocus; // ie 369 if ( window.addEventListener ) { 370 dw_Event.add(document, 'focus', dw_Tooltip.checkActuatorFocus, true); 371 } 372 } 373} 374 375dw_Tooltip.checkActuatorMouseover = function (e) { 376 var tgt = dw_Event.getTarget(e); var tipAct = null; 377 // limit number of loops 378 var ctr = 0; var maxCnt = dw_Tooltip.maxLoops; 379 do { 380 if ( tipAct = dw_Tooltip.getActuatorInfo(tgt) ) { 381 var msg = tipAct.msg; var id = tipAct.id; 382 dw_Tooltip.activate(e, tgt, msg, id); 383 if ( window.attachEvent ) { // avoid multiples for ie (?) 384 dw_Event.remove( tgt, 'mouseout', dw_Tooltip.deactivate); 385 } 386 dw_Event.add( tgt, 'mouseout', dw_Tooltip.deactivate); 387 break; 388 } 389 ctr++; 390 } while ( ctr < maxCnt && (tgt = tgt.parentNode) ); 391 392} 393 394dw_Tooltip.checkActuatorFocus = function (e) { 395 e = e? e: window.event; var tipAct = null; 396 var tgt = dw_Event.getTarget(e); 397 if ( tgt && (tipAct = dw_Tooltip.getActuatorInfo(tgt) ) ) { 398 if ( dw_Tooltip.active && tgt == dw_Tooltip.actuator ) { 399 return; // if already activated onmouseover 400 } 401 var msg = tipAct.msg; var id = tipAct.id; 402 dw_Tooltip.activate(e, tgt, msg, id); 403 if ( window.attachEvent ) { 404 tgt.onfocusout = dw_Tooltip.deactivate; 405 } else { 406 dw_Event.add( tgt, 'blur', dw_Tooltip.deactivate, true); 407 } 408 } 409} 410 411// Check whether the target is an actuator and the content can be located 412// Either the content itself or the identifier in content_vars will be returned in obj {msg: msg, id: id} 413dw_Tooltip.getActuatorInfo = function (tgt) { 414 var qual = dw_Tooltip.defaultProps['actuatorQual'] || 'actuatorClass'; 415 var source = dw_Tooltip.defaultProps['content_source'] || 'content_vars'; 416 var msg = '', id = ''; 417 dw_Tooltip.resetReqFlags(); 418 switch (qual) { 419 case 'actuatorClass' :
420 var cls = dw_Tooltip.getTipClass(tgt.className); 421 if (!cls) break; 422 if ( source == 'content_vars' ) { 423 id = (dw_Tooltip.content_vars && dw_Tooltip.content_vars[cls])? cls: ''; 424 } else if ( source == 'class_id' ) { 425 var el = document.getElementById(cls); 426 if (el) msg = el.innerHTML; 427 } 428 break; 429 case 'queryVal' : 430 var queryVal = dw_Tooltip.defaultProps['queryVal']; 431 var val = queryVal? dw_getValueFromQueryString(queryVal, tgt ): ''; 432 id = (val && dw_Tooltip.content_vars && dw_Tooltip.content_vars[val])? val: ''; 433 // Even if the content source is ajax, would check content_vars (see below) 434 // dw_updateTooltip should be set up to save results in content_vars 435 if ( val && source == 'ajax' && !id ) { 436 // Something to display in the tooltip while awaiting response. Empty string won't suffice! 437 msg = 'Retrieving info ...'; 438 dw_Tooltip.pendingReq = true; 439 var queryData = encodeURIComponent(queryVal) + '=' + encodeURIComponent(val); 440 dw_TooltipRequest( queryData, val ); // val passed as means to save response 441 } 442 break; 443 case 'id' : 444 id = (tgt.id && dw_Tooltip.content_vars && dw_Tooltip.content_vars[tgt.id])? tgt.id: ''; 445 break; 446 } 447 //if ( id && !msg ) { // check content_vars (for previously saved ajax result, or more complex data for ajax request) } 448 if ( id || msg ) { 449 return {msg: msg, id: id} 450 } 451 return false; 452} 453 454// check so don't overwrite response if already received (localhost speed) 455dw_Tooltip.resetReqFlags = function () { 456 this.respRecd = false; 457 this.pendingReq = false; 458} 459 460///////////////////////////////////////////////////////////////////// 461// Helper functions 462function dw_mouseleave(e, oNode) { 463 e = dw_Event.DOMit(e); 464 var toEl = e.relatedTarget? e.relatedTarget: e.toElement? e.toElement: null; 465 if ( oNode != toEl && !dw_contained(toEl, oNode) ) { 466 return true; 467 } 468 return false; 469} 470 471function dw_contained(oNode, oCont) { 472 if (!oNode) return; // in case alt-tab away while hovering (prevent error) 473 while ( oNode = oNode.parentNode ) if ( oNode == oCont ) return true; 474 return false; 475} 476 477// Get position of element in page (treacherous cross-browser territory! Don't expect perfect results) 478// can get weird results in ie 479function dw_getPageOffsets(el) { 480 var left = 0, top = 0; 481 do { 482 left += el.offsetLeft; 483 top += el.offsetTop; 484 } while (el = el.offsetParent); 485 return { x:left, y:top }; 486} 487 488// obj: link or window.location 489function dw_getValueFromQueryString(name, obj) { 490 obj = obj? obj: window.location; 491 if (obj.search && obj.search.indexOf(name != -1) ) { 492 var pairs = obj.search.slice(1).split("&"); // name/value pairs 493 var set; 494 for (var i=0; pairs[i]; i++) { 495 set = pairs[i].split("="); // Check each pair for match on name 496 if ( set[0] == name && set[1] ) { 497 return set[1]; 498 } 499 } 500 } 501 return ''; 502} 503 504///////////////////////////////////////////////////////////////////// 505// Reminder about licensing requirements 506// See Terms of Use at www.dyn-web.com/business/terms.php 507// OK to remove after purchasing a license or if using on a personal site. 508function dw_checkAuth() { 509 var loc = window.location.hostname.toLowerCase(); 510 var msg = 'A license is required for all but personal use of this code.\n' + 511 'Please adhere to our Terms of Use if you use dyn-web code.'; 512 if ( !( loc == '' || loc == '127.0.0.1' || loc.indexOf('localhost') != -1 513 || loc.indexOf('192.168.') != -1 || loc.indexOf('dyn-web.com') != -1 ) ) { 514 alert(msg); 515 } 516} 517//dw_Event.add( window, 'load', dw_checkAuth); 518/////////////////////////////////////////////////////////////////////
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.