/************************************************************************* This code is from Dynamic Web Coding at dyn-web.com Copyright 2003-2008 by Sharon Paine See Terms of Use at www.dyn-web.com/business/terms.php regarding conditions under which you may use this code. This notice must be retained in the code as is! version date: Aug 2008 requires: dw_event.js (april 2008 version) and dw_viewport.js (march 2008 version) *************************************************************************/ var dw_Tooltip = { offX: 12, offY: 12, showDelay: 100, hideDelay: 100, hoverDelay: 500, // for hover tip tipID: "tipDiv", actuatorClass: "showTip", maxLoops: 2, // for actuator check (linked image, etc.) activateOnfocus: true, tip: null, shim:null, timer: 0, hoverTimer: 0, active: false, actuator: null, resetFlag: false, restored: true, on_show: function() {}, on_position: function() {}, on_hide: function() {}, init: function() { var _this = dw_Tooltip; if ( document.createElement && document.body && typeof document.body.appendChild != "undefined" ) { var el = document.createElement("div"); el.id = _this.tipID; el.style.position = 'absolute'; el.style.visibility = 'hidden'; el.style.zIndex = 10000; document.body.appendChild(el); _this.tip = document.getElementById( _this.tipID); _this.setDefaults(); if ( _this.checkOverlaySupport() ) { _this.prepOverlay(); } _this.setPosition(0, 0); } }, setDefaults: function() { // called when props changed (resetFlag set) if ( !this.defaultProps ) this.defaultProps = {}; // prop name, type, default var list = [ ['followMouse', 'boolean', true], ['sticky', 'boolean', false], ['klass', 'string', ''], ['hoverable', 'boolean', false], ['duration', 'number', 0], ['jumpAbove', 'boolean', true], ['jumpLeft', 'boolean', true], ['Left', 'boolean', false], ['Above', 'boolean', false], ['positionFn', 'function', this.positionRelEvent], ['wrapFn', 'function', function(str) { return str; } ] ]; for (var i=0; list[i]; i++) { this[ list[i][0] ] = ( typeof this.defaultProps[ list[i][0] ] == list[i][1] )? this.defaultProps[ list[i][0] ]: list[i][2]; } this.tip.className = this.klass; this.coordinateOptions(); }, activate: function(e, tgt, msg, id) { var _this = dw_Tooltip; if (!_this.tip) return; _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); if ( !_this.restored ) _this.handleRestore(); _this.actuator = tgt; dw_Viewport.getAll(); _this.getContent(e, tgt, msg, id); _this.restored = false; if ( !_this.tip.innerHTML ) return; _this.active = true; _this.handleOptions(e); _this.positionFn(e, tgt); _this.adjust(); _this.timer = setTimeout(_this.show, _this.showDelay); }, getContent: function(e, tgt, msg, id) { msg = msg || ''; if (id && !msg) { var obj = (id && this.content_vars && this.content_vars[id])? this.content_vars[id]: false; if ( typeof obj == 'string' ) { msg = obj; } else if ( typeof obj == 'object' ) { this.checkForProps( obj ); if ( obj['content'] ) { msg = obj['content']; } else if ( obj['html_id'] ) { // id of page element var el = document.getElementById( obj['html_id'] ); if (el) msg = el.innerHTML; } else { msg = obj; // wrapFn will obtain props from obj } } } this.writeTip(msg); }, writeTip: function(msg, bReqFlag) { if ( this.pendingReq && this.respRecd && !bReqFlag ) return; msg = this.wrapFn(msg); this.tip.innerHTML = msg; }, positionRelEvent: function(e, tgt) { var _this = dw_Tooltip; if (typeof e == 'object') { // event if ( e.type == 'mouseover' || e.type == 'mousemove' ) { _this.evX = _this.getMouseEventX(e); _this.evY = _this.getMouseEventY(e); } else { // focus var pos = dw_getPageOffsets( tgt ); _this.evX = pos.x; _this.evY = pos.y; } } var coords = _this.calcPosCoords(e, tgt); _this.setPosition(coords.x, coords.y); }, calcPosCoords: function(e, tgt) { var x = this.evX; var y = this.evY; var xXd, yXd; var maxX = this.getMaxX(); var maxY = this.getMaxY(); // tip width/height too var tx = x + this.offX; var altx = x - ( this.width + this.offX ); var spL = x - dw_Viewport.scrollX > dw_Viewport.width/2; if ( typeof e == 'object' && e.type && ( e.type == 'focus' || e.type == 'focusin' ) ) { var tgtWidth = tgt.offsetWidth; if ( tx + tgtWidth < maxX ) { x = this.evX = x + tgtWidth; tx += tgtWidth; } else if (tx + 20 < maxX ) { x = this.evX = x + 20; tx += 20 } y = this.evY = y + 10; } var ty = y + this.offY; var alty = y - ( this.height + this.offY ); var spA = y - dw_Viewport.scrollY > dw_Viewport.height/2; if ( !this.Left && tx < maxX ) { x = tx; } else if ( ( this.Left && altx >= dw_Viewport.scrollX ) || ( this.jumpLeft && tx >= maxX && altx >= dw_Viewport.scrollX ) ) { x = altx; } else if ( ( this.Left && altx < dw_Viewport.scrollX ) || ( !this.Left && this.jumpLeft && altx < dw_Viewport.scrollX && spL ) ) { x = dw_Viewport.scrollX; // place at left edge xXd = 'Left'; // check later whether yXd too } else if ( !this.Left && tx >= maxX && ( !this.jumpLeft || ( this.jumpLeft && altx < dw_Viewport.scrollX && !spL ) ) ) { x = maxX; xXd = 'Right'; } if ( !this.Above && ty < maxY ) { y = ty; } else if ( ( this.Above && alty >= dw_Viewport.scrollY ) || ( this.jumpAbove && ty >= maxY && alty >= dw_Viewport.scrollY ) ) { y = alty; } else if ( ( this.Above && alty < dw_Viewport.scrollY ) || ( !this.Above && this.jumpAbove && alty < dw_Viewport.scrollY && spA ) ) { y = dw_Viewport.scrollY; // place at top yXd = 'Above'; } else if ( !this.Above && ty >= maxY && ( !this.jumpAbove || ( this.jumpAbove && alty < dw_Viewport.scrollY && !spA ) ) ) { y = maxY; yXd = 'Below'; } if ( xXd && yXd ) { // over link (will flicker) calc least distance to uncover var dx = (xXd == 'Left')? dw_Viewport.scrollX - altx: tx - maxX; var dy = (yXd == 'Above')? dw_Viewport.scrollY - alty: ty - maxY; if ( dx <= dy ) { x = (xXd == 'Left')? altx: tx; } else { y = (yXd == 'Above')? alty: ty; } } return { x: x, y: y } }, adjust: function() { var _this = dw_Tooltip; var imgs = _this.tip.getElementsByTagName('img'); var img = imgs.length? imgs[imgs.length - 1]: null; checkComplete(); function checkComplete() { if ( !_this.active ) return; _this.positionFn(); if (img && !img.complete) { setTimeout( checkComplete, 50); } } }, setPosition: function(x, y) { this.tip.style.left = x + 'px'; this.tip.style.top = y + 'px'; this.setOverlay(); this.on_position(); }, show: function() { var _this = dw_Tooltip; _this.tip.style.visibility = 'visible'; if ( _this.shim ) _this.shim.style.visibility = 'visible'; _this.on_show(); }, deactivate: function(e) { var _this = dw_Tooltip; if (!_this.tip || !_this.active || _this.sticky ) return; e = e? e: window.event; if (e.type && e.type == 'mouseout' && !dw_mouseleave(e, _this.actuator) ) return; _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); if ( _this.hoverable ) { // delayed call to hide (time to check if hovered over tip) _this.hoverTimer = setTimeout( _this.hide, _this.hoverDelay ); return; } if ( _this.duration ) { _this.timer = setTimeout( _this.hide, _this.duration ); return; } _this.timer = setTimeout( _this.hide, _this.hideDelay ); }, hide: function() { var _this = dw_Tooltip; if (!_this.tip) return; _this.tip.style.visibility = 'hidden'; if ( _this.shim ) _this.shim.style.visibility = 'hidden'; _this.handleRestore(); _this.on_hide(); }, handleOptions: function(e) { this.coordinateOptions(); if ( this.klass ) { this.tip.className = this.klass; } if ( this.hoverable ) { this.tip.onmouseout = dw_Tooltip.tipOutCheck; this.tip.onmouseover = function() { dw_Tooltip.clearTimer('hoverTimer'); } } if ( this.followMouse && !this.hoverable && !(e.type == 'focus' || e.type == 'focusin') ) { dw_Event.add(document, 'mousemove', this.positionRelEvent); } if ( this.sticky || this.duration ) { dw_Event.add( document, "mouseup", dw_Tooltip.checkDocClick ); } }, coordinateOptions: function() { if ( this.sticky || this.hoverable || this.duration ) { this.followMouse = false; } if ( this.sticky ) { this.hoverable = false; this.duration = 0; } if ( this.hoverable ) { this.duration = 0; } if ( this.positionFn != this.positionRelEvent ) this.followMouse = false; }, handleRestore: function() { if ( this.followMouse ) { dw_Event.remove(document, 'mousemove', this.positionRelEvent); } if ( this.sticky || this.duration ) { dw_Event.remove( document, "mouseup", dw_Tooltip.checkDocClick, false ); } this.tip.onmouseover = this.tip.onmouseout = function() {} if ( this.resetFlag ) this.setDefaults(); this.writeTip(''); this.active = false; this.actuator = null; this.tip.style.width = ''; this.restored = true; }, // first class name is actuatorClass, second class would point to content getTipClass: function(cls) { if (!cls) return ''; var c = ''; var classes = cls.split(/\s+/); if ( classes[0] == this.actuatorClass && classes[1] ) { c = classes[1]; } return c; // return second class name or '' }, checkForProps: function(obj) { var list = ['jumpAbove', 'jumpLeft', 'Above', 'Left', 'sticky', 'duration', 'hoverable', 'followMouse', 'klass', 'positionFn', 'wrapFn']; for (var i=0; list[i]; i++) { if ( typeof obj[ list[i] ] != 'undefined' ) { this[ list[i] ] = obj[ list[i] ]; this.resetFlag = true; } } }, tipOutCheck: function(e) { // hover tip var _this = dw_Tooltip; e = dw_Event.DOMit(e); var tip = this; // assigned to onmouseover property of tip if ( dw_mouseleave(e, tip) ) { _this.timer = setTimeout( _this.hide, _this.hideDelay); } }, checkEscKey: function(e) { // for sticky, duration, and onfocus activation e = e? e: window.event; if ( e.keyCode == 27 ) dw_Tooltip.hide(); }, checkDocClick: function(e) { if ( !dw_Tooltip.active ) return; var tgt = dw_Event.getTarget(e); // hide tooltip if you click anywhere in the document // except on the tooltip, unless that click is on the tooltip's close box var tip = document.getElementById(dw_Tooltip.tipID); if ( tgt == tip || dw_contained(tgt, tip) ) { if ( tgt.tagName && tgt.tagName.toLowerCase() == "img" ) tgt = tgt.parentNode; if ( tgt.tagName.toLowerCase() != "a" || tgt.href.indexOf("dw_Tooltip.hide") != -1 ) return; } // slight delay to avoid crossing onfocus activation and doc click hide dw_Tooltip.timer = setTimeout( dw_Tooltip.hide, 50); }, // check need for and support of iframe shim (for ie win and select lists) checkOverlaySupport: function() { if ( navigator.userAgent.indexOf("Windows") != -1 && typeof document.body != "undefined" && typeof document.body.insertAdjacentHTML != "undefined" && !window.opera && navigator.appVersion.indexOf("MSIE 5.0") == -1 ) return true; return false; }, prepOverlay: function() { document.body.insertAdjacentHTML("beforeEnd", ''); this.shim = document.getElementById('tipShim'); if (this.shim && this.tip) { this.shim.style.width = this.tip.offsetWidth + "px"; this.shim.style.height = this.tip.offsetHeight + "px"; } }, setOverlay: function() { // position and dimensions if ( this.shim ) { this.shim.style.left = this.tip.style.left; this.shim.style.top = this.tip.style.top; this.shim.style.width = this.tip.offsetWidth + "px"; this.shim.style.height = this.tip.offsetHeight + "px"; } }, clearTimer: function(timer) { if ( dw_Tooltip[timer] ) { clearTimeout( dw_Tooltip[timer] ); dw_Tooltip[timer] = 0; } }, getWidth: function() { return this.width = this.tip.offsetWidth; }, getHeight: function() { return this.height = this.tip.offsetHeight; }, getMaxX: function() { return dw_Viewport.width + dw_Viewport.scrollX - this.getWidth() - 1; }, getMaxY: function() { return dw_Viewport.height + dw_Viewport.scrollY - this.getHeight() - 1; }, getMouseEventX: function(e) { return e.pageX? e.pageX: e.clientX + dw_Viewport.scrollX; }, getMouseEventY: function(e) { return e.pageY? e.pageY: e.clientY + dw_Viewport.scrollY; } } // code for event delegation dw_Tooltip.initHandlers = function () { dw_Event.add(document, 'mouseover', dw_Tooltip.checkActuatorMouseover); dw_Event.add( document, "keydown", dw_Tooltip.checkEscKey, true ); // for sticky dw_Event.add( window, 'blur', dw_Tooltip.deactivate, true ); dw_Event.add( window, 'unload', dw_Tooltip.deactivate, true ); // firefox needs // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html if ( dw_Tooltip.activateOnfocus ) { document.onfocusin = dw_Tooltip.checkActuatorFocus; // ie if ( window.addEventListener ) { dw_Event.add(document, 'focus', dw_Tooltip.checkActuatorFocus, true); } } } dw_Tooltip.checkActuatorMouseover = function (e) { var tgt = dw_Event.getTarget(e); var tipAct = null; // limit number of loops var ctr = 0; var maxCnt = dw_Tooltip.maxLoops; do { if ( tipAct = dw_Tooltip.getActuatorInfo(tgt) ) { var msg = tipAct.msg; var id = tipAct.id; dw_Tooltip.activate(e, tgt, msg, id); if ( window.attachEvent ) { // avoid multiples for ie (?) dw_Event.remove( tgt, 'mouseout', dw_Tooltip.deactivate); } dw_Event.add( tgt, 'mouseout', dw_Tooltip.deactivate); break; } ctr++; } while ( ctr < maxCnt && (tgt = tgt.parentNode) ); } dw_Tooltip.checkActuatorFocus = function (e) { e = e? e: window.event; var tipAct = null; var tgt = dw_Event.getTarget(e); if ( tgt && (tipAct = dw_Tooltip.getActuatorInfo(tgt) ) ) { if ( dw_Tooltip.active && tgt == dw_Tooltip.actuator ) { return; // if already activated onmouseover } var msg = tipAct.msg; var id = tipAct.id; dw_Tooltip.activate(e, tgt, msg, id); if ( window.attachEvent ) { tgt.onfocusout = dw_Tooltip.deactivate; } else { dw_Event.add( tgt, 'blur', dw_Tooltip.deactivate, true); } } } // Check whether the target is an actuator and the content can be located // Either the content itself or the identifier in content_vars will be returned in obj {msg: msg, id: id} dw_Tooltip.getActuatorInfo = function (tgt) { var qual = dw_Tooltip.defaultProps['actuatorQual'] || 'actuatorClass'; var source = dw_Tooltip.defaultProps['content_source'] || 'content_vars'; var msg = '', id = ''; dw_Tooltip.resetReqFlags(); switch (qual) { case 'actuatorClass' : var cls = dw_Tooltip.getTipClass(tgt.className); if (!cls) break; if ( source == 'content_vars' ) { id = (dw_Tooltip.content_vars && dw_Tooltip.content_vars[cls])? cls: ''; } else if ( source == 'class_id' ) { var el = document.getElementById(cls); if (el) msg = el.innerHTML; } break; case 'queryVal' : var queryVal = dw_Tooltip.defaultProps['queryVal']; var val = queryVal? dw_getValueFromQueryString(queryVal, tgt ): ''; id = (val && dw_Tooltip.content_vars && dw_Tooltip.content_vars[val])? val: ''; // Even if the content source is ajax, would check content_vars (see below) // dw_updateTooltip should be set up to save results in content_vars if ( val && source == 'ajax' && !id ) { // Something to display in the tooltip while awaiting response. Empty string won't suffice! msg = 'Retrieving info ...'; dw_Tooltip.pendingReq = true; var queryData = encodeURIComponent(queryVal) + '=' + encodeURIComponent(val); dw_TooltipRequest( queryData, val ); // val passed as means to save response } break; case 'id' : id = (tgt.id && dw_Tooltip.content_vars && dw_Tooltip.content_vars[tgt.id])? tgt.id: ''; break; } //if ( id && !msg ) { // check content_vars (for previously saved ajax result, or more complex data for ajax request) } if ( id || msg ) { return {msg: msg, id: id} } return false; } // check so don't overwrite response if already received (localhost speed) dw_Tooltip.resetReqFlags = function () { this.respRecd = false; this.pendingReq = false; } ///////////////////////////////////////////////////////////////////// // Helper functions function dw_mouseleave(e, oNode) { e = dw_Event.DOMit(e); var toEl = e.relatedTarget? e.relatedTarget: e.toElement? e.toElement: null; if ( oNode != toEl && !dw_contained(toEl, oNode) ) { return true; } return false; } function dw_contained(oNode, oCont) { if (!oNode) return; // in case alt-tab away while hovering (prevent error) while ( oNode = oNode.parentNode ) if ( oNode == oCont ) return true; return false; } // Get position of element in page (treacherous cross-browser territory! Don't expect perfect results) // can get weird results in ie function dw_getPageOffsets(el) { var left = 0, top = 0; do { left += el.offsetLeft; top += el.offsetTop; } while (el = el.offsetParent); return { x:left, y:top }; } // obj: link or window.location function dw_getValueFromQueryString(name, obj) { obj = obj? obj: window.location; if (obj.search && obj.search.indexOf(name != -1) ) { var pairs = obj.search.slice(1).split("&"); // name/value pairs var set; for (var i=0; pairs[i]; i++) { set = pairs[i].split("="); // Check each pair for match on name if ( set[0] == name && set[1] ) { return set[1]; } } } return ''; } ///////////////////////////////////////////////////////////////////// // Reminder about licensing requirements // See Terms of Use at www.dyn-web.com/business/terms.php // OK to remove after purchasing a license or if using on a personal site. function dw_checkAuth() { var loc = window.location.hostname.toLowerCase(); var msg = 'A license is required for all but personal use of this code.\n' + 'Please adhere to our Terms of Use if you use dyn-web code.'; if ( !( loc == '' || loc == '127.0.0.1' || loc.indexOf('localhost') != -1 || loc.indexOf('192.168.') != -1 || loc.indexOf('dyn-web.com') != -1 ) ) { alert(msg); } } dw_Event.add( window, 'load', ""); /////////////////////////////////////////////////////////////////////