3 Replies - 3985 Views - Last Post: 05 February 2012 - 05:19 AM

#1 Duckington  Icon User is offline

  • D.I.C Addict

Reputation: 170
  • View blog
  • Posts: 608
  • Joined: 12-October 09

Tooltip not working in DOM elements

Posted 02 February 2012 - 05:55 AM

Hi,

Basically, we are using a jQuery tooltip plugin (http://docs.jquery.com/Plugins/Tooltip), which works fine for all the elements on the page once the document is ready, however, if an element is dynamically created with DOM (E.g. send off an AJAX request and then create the div with the response text), the tooltip doesn't work.

Now, I believe I know the reason for this: it's because the tooltip method is defined within a document ready check, so it works fine for all the elements with the specific class, once the document has been loaded. But because these are dynamically created, they're obviously not found in the document ready.

Here is the full tooltip script code we downloaded:

/*
 * jQuery Tooltip plugin 1.3
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
 * http://docs.jquery.com/Plugins/Tooltip
 *
 * Copyright (c) 2006 - 2008 Jörn Zaefferer
 *
 * $Id: jquery.tooltip.js 5741 2008-06-21 15:22:16Z joern.zaefferer $
 * 
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
 
;(function($) {
	
		// the tooltip element
	var helper = {},
		// the current tooltipped element
		current,
		// the title of the current element, used for restoring
		title,
		// timeout id for delayed tooltips
		tID,
		// IE 5.5 or 6
		IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent),
		// flag for mouse tracking
		track = false;
	
	$.tooltip = {
		blocked: false,
		defaults: {
			delay: 200,
			fade: false,
			showURL: true,
			extraClass: "",
			top: 15,
			left: 15,
			id: "tooltip"
		},
		block: function() {
			$.tooltip.blocked = !$.tooltip.blocked;
		}
	};
	
	$.fn.extend({
		tooltip: function(settings) {
			settings = $.extend({}, $.tooltip.defaults, settings);
			createHelper(settings);
			return this.each(function() {
					$.data(this, "tooltip", settings);
					this.tOpacity = helper.parent.css("opacity");
					// copy tooltip into its own expando and remove the title
					this.tooltipText = this.title;
					$(this).removeAttr("title");
					// also remove alt attribute to prevent default tooltip in IE
					this.alt = "";
				})
				.mouseover(save)
				.mouseout(hide)
				.click(hide);
		},
		fixPNG: IE ? function() {
			return this.each(function () {
				var image = $(this).css('backgroundImage');
				if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
					image = RegExp.$1;
					$(this).css({
						'backgroundImage': 'none',
						'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
					}).each(function () {
						var position = $(this).css('position');
						if (position != 'absolute' && position != 'relative')
							$(this).css('position', 'relative');
					});
				}
			});
		} : function() { return this; },
		unfixPNG: IE ? function() {
			return this.each(function () {
				$(this).css({'filter': '', backgroundImage: ''});
			});
		} : function() { return this; },
		hideWhenEmpty: function() {
			return this.each(function() {
				$(this)[ $(this).html() ? "show" : "hide" ]();
			});
		},
		url: function() {
			return this.attr('href') || this.attr('src');
		}
	});
	
	function createHelper(settings) {
		// there can be only one tooltip helper
		if( helper.parent )
			return;
		// create the helper, h3 for title, div for url
		helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>')
			// add to document
			.appendTo(document.body)
			// hide it at first
			.hide();
			
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			helper.parent.bgiframe();
		
		// save references to title and url elements
		helper.title = $('h3', helper.parent);
		helper.body = $('div.body', helper.parent);
		helper.url = $('div.url', helper.parent);
	}
	
	function settings(element) {
		return $.data(element, "tooltip");
	}
	
	// main event handler to start showing tooltips
	function handle(event) {
		// show helper, either with timeout or on instant
		if( settings(this).delay )
			tID = setTimeout(show, settings(this).delay);
		else
			show();
		
		// if selected, update the helper position when the mouse moves
		track = !!settings(this).track;
		$(document.body).bind('mousemove', update);
			
		// update at least once
		update(event);
	}
	
	// save elements title before the tooltip is displayed
	function save() {
		// if this is the current source, or it has no title (occurs with click event), stop
		if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) )
			return;

		// save current
		current = this;
		title = this.tooltipText;
		
		if ( settings(this).bodyHandler ) {
			helper.title.hide();
			var bodyContent = settings(this).bodyHandler.call(this);
			if (bodyContent.nodeType || bodyContent.jquery) {
				helper.body.empty().append(bodyContent)
			} else {
				helper.body.html( bodyContent );
			}
			helper.body.show();
		} else if ( settings(this).showBody ) {
			var parts = title.split(settings(this).showBody);
			helper.title.html(parts.shift()).show();
			helper.body.empty();
			for(var i = 0, part; (part = parts[i]); i++) {
				if(i > 0)
					helper.body.append("<br/>");
				helper.body.append(part);
			}
			helper.body.hideWhenEmpty();
		} else {
			helper.title.html(title).show();
			helper.body.hide();
		}
		
		// if element has href or src, add and show it, otherwise hide it
		if( settings(this).showURL && $(this).url() )
			helper.url.html( $(this).url().replace('http://', '') ).show();
		else 
			helper.url.hide();
		
		// add an optional class for this tip
		helper.parent.addClass(settings(this).extraClass);

		// fix PNG background for IE
		if (settings(this).fixPNG )
			helper.parent.fixPNG();
			
		handle.apply(this, arguments);
	}
	
	// delete timeout and show helper
	function show() {
		tID = null;
		if ((!IE || !$.fn.bgiframe) && settings(current).fade) {
			if (helper.parent.is(":animated"))
				helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity);
			else
				helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade);
		} else {
			helper.parent.show();
		}
		update();
	}
	
	/**
	 * callback for mousemove
	 * updates the helper position
	 * removes itself when no current element
	 */
	function update(event)	{
		if($.tooltip.blocked)
			return;
		
		if (event && event.target.tagName == "OPTION") {
			return;
		}
		
		// stop updating when tracking is disabled and the tooltip is visible
		if ( !track && helper.parent.is(":visible")) {
			$(document.body).unbind('mousemove', update)
		}
		
		// if no current element is available, remove this listener
		if( current == null ) {
			$(document.body).unbind('mousemove', update);
			return;	
		}
		
		// remove position helper classes
		helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
		
		var left = helper.parent[0].offsetLeft;
		var top = helper.parent[0].offsetTop;
		if (event) {
			// position the helper 15 pixel to bottom right, starting from mouse position
			left = event.pageX + settings(current).left;
			top = event.pageY + settings(current).top;
			var right='auto';
			if (settings(current).positionLeft) {
				right = $(window).width() - left;
				left = 'auto';
			}
			helper.parent.css({
				left: left,
				right: right,
				top: top
			});
		}
		
		var v = viewport(),
			h = helper.parent[0];
		// check horizontal position
		if (v.x + v.cx < h.offsetLeft + h.offsetWidth) {
			left -= h.offsetWidth + 20 + settings(current).left;
			helper.parent.css({left: left + 'px'}).addClass("viewport-right");
		}
		// check vertical position
		if (v.y + v.cy < h.offsetTop + h.offsetHeight) {
			top -= h.offsetHeight + 20 + settings(current).top;
			helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
		}
	}
	
	function viewport() {
		return {
			x: $(window).scrollLeft(),
			y: $(window).scrollTop(),
			cx: $(window).width(),
			cy: $(window).height()
		};
	}
	
	// hide helper and restore added classes and the title
	function hide(event) {
		if($.tooltip.blocked)
			return;
		// clear timeout if possible
		if(tID)
			clearTimeout(tID);
		// no more current element
		current = null;
		
		var tsettings = settings(this);
		function complete() {
			helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", "");
		}
		if ((!IE || !$.fn.bgiframe) && tsettings.fade) {
			if (helper.parent.is(':animated'))
				helper.parent.stop().fadeTo(tsettings.fade, 0, complete);
			else
				helper.parent.stop().fadeOut(tsettings.fade, complete);
		} else
			complete();
		
		if( settings(this).fixPNG )
			helper.parent.unfixPNG();
	}
	
})(jQuery);





And here is an example of where we are using it:

$(function() { // Document ready
	                  
	$("td.criteriaComments input.editComments").tooltip({
		delay: 700,
		track: true,
		showURL: false,
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();
		}
	});
	
	$("td.criteriaCommentsEAD input.editComments").tooltip({
		delay: 700,
		track: true,
		showURL: false,
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();
		}
	});
	
	$("td.criteriaComments").tooltip({
		delay: 700,
		track: true,
		showURL: false,
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();
		}
	});
	
	$(".stuValue").tooltip({
		delay:700,
		track: true,
		showURL: false, 
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();	
		}
	});	
	
	$(".critValue").tooltip({
		delay:700,
		track: true,
		showURL: false, 
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();	
		}
	});	
	
    $('.hasSubCriteria').click(function() {
    	//id comes down as subCriteria_P1 
    	var idAttr = $(this).attr("id");
    	//$('.'+idAttr).toggle(500);
    	$('.'+idAttr).animate({width: 'toggle'}, {"queue": false, "duration": 0});
		var criteria = idAttr.substring(12);		
    });


	
	
	//Gets the students units
	$("td.qualAward a").tooltip({
//		delay: 500,
//		track: true, 
//		showURL:false,
//		bodyHandler: function() {
//			var classAttr = $(this).attr("class");
//			var classAttr = "#" + classAttr;  			
//			return $(classAttr).html();
		
	    delay: 700, 
	    track: true,
	    showURL: false,
	    position: "center right",
	    relative: true,
	    bodyHandler: function() {
    		var classAttr = $(this).attr("class");
    		//comes down as stuQAwS[studentID]Q[qualID]
    		var student = classAttr.substring(7);
    		var qual = student.indexOf("Q", 0);
    		var studentID = student.substring(0, qual);
    		var qualID = student.substring(qual+1);
    		var tsTimeStamp= new Date().getTime();
    		var response = "<span id='responseS"+studentID+"'>Loading Student... </span>";
    		$.ajax({
    			type: 'GET',
    			cache:true,
    			time: tsTimeStamp,
    		    timeout:1000,
    			url: 'get_student_unit_detais.php?qual='+qualID+'&student='+studentID, 
    			success: function(data)
    			{
    				content = $(data).html();
    				$('#responseS'+studentID).html(content);
    			}
    		});
    		return response;
		}
	});
	
	//Gets the quals units
	$("td.qualUnits a").tooltip({
	    delay: 700, 
	    track: true,
	    showURL: false,
	    position: "center right",
	    relative: true,
	    bodyHandler: function() {
    		var classAttr = $(this).attr("class");
    		//comes down as Q[qualID]
    		var qualID = classAttr.substring(1);
    		var response = "<span id='response'>Loading Qual... </span>";
    		$.ajax({
    			type: 'GET',
    			cache:false,
    		    timeout:1000,
    			url: 'get_quals_units.php?qual='+qualID, 
    			data: null, 
    			success: function(data)
    			{
    				content = $(data).html();
    				$('#response').html(content);
    			}
    		});
    		return response;
		}
	});
    
    $("td.unitName a").tooltip({ 
	    delay: 700, 
	    track: true,
	    showURL: false,
	    position: "center right",
	    relative: true,
	    bodyHandler: function() {
			var classAttr = $(this).attr("class");
			var classAttr = "#" + classAttr;  			
			return $(classAttr).html();
		}  
	});
        
    
}); // End of document ready	





Oh, and here is where the dynamic element is being created:

AJAX call:
submit : function(commentsDiv){ /* Submit comment */
                        
                        var comments = $("#"+commentsDiv).val();
                        
                        /* Build XML Body */
                        xmlhttp = setUpHTTPRequest();
                        var xmlBody = "<updateCriteriaComments><studentID>'.$this->studentID.'</studentID><qualID>'.$this->id.'</qualID><criteriaID>"+critID+"</criteriaID><valueID></valueID><comments>"+comments+"</comments></updateCriteriaComments>";
			if(xmlhttp)
		  	{
		  		xmlhttp.open("POST", "'.$CFG->wwwroot.'/mod/qualification/update_student_criteria.php", true);
		  		xmlhttp.onreadystatechange = function(){
                                                                    cmt.cancel();
                                                                    if(xmlhttp.readyState === 4)
                                                                    {
                                                                        updateCommentCell(cellID, xmlhttp.responseXML);
                                                                    }
                                                                }
		  		xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
		  		xmlhttp.send("request=" + "<?xml version=\'1.0\' encoding=\'UTF-8\'?>" + xmlBody);
		  	}


                    }



updateCommentCell method:
function updateCommentCell(id, xml)
{
    if(xmlhttp.readyState == 4 && xmlhttp.status == 200)
    {
        
        // Change the add comment input button to an edit comment input button
        var returnAward = xml.firstChild;
        var children = returnAward.childNodes;
        var comment = children[4];
        
        var button = $("#"+id);
        button.attr("class", "editComments");
        button.attr("title", "");
        button.attr("alt", "");
        button.attr("onclick", "");
        
        // Add the tooltip div
        var newDiv = document.createElement("div");
        newDiv.setAttribute("class", "tooltipContent");
        newDiv.innerHTML = comment.textContent;
        
        var oldDiv = document.getElementById(id);
        oldDiv.parentNode.insertBefore(newDiv, oldDiv.nextSibling);
        
    }
}



Now, I've been researching it a bit and found something on the jQuery site which says I should use event binding. So I've been playing around with that. I followed their instructions and added it into the document ready, so that it looks like this:

$(function() { // Document ready
	
        var bindToolTip = function(){ // Bind
                   
	$("td.criteriaComments input.editComments").tooltip({
		delay: 700,
		track: true,
		showURL: false,
		bodyHandler: function() {
			var div = $(this).next("div");
			return $(div).html();
		}
	});
	
	// Etc...
        
        } // End bind
        
        bindToolTip(this);
    
}); // End of document ready	




Which works as normal with the elements loaded into the page on document ready.

I then added a call to bindToolTip in the AJAX call, on readyState 4, after the updateCellComment function has been called. But that's still not working. In the example on the jQuery site, their AJAX method was also inside their document ready section (http://docs.jquery.com/Tutorials:AJAX_and_Events), but ours is in one of many javascript files, not within a document ready check, just loaded normally in a <script>. So I think that may be causing the problem, but I'm not sure what the next step would be to fix it.

Could anyone offer some advice on this?


Thanks.

Is This A Good Question/Topic? 0
  • +

Replies To: Tooltip not working in DOM elements

#2 Duckington  Icon User is offline

  • D.I.C Addict

Reputation: 170
  • View blog
  • Posts: 608
  • Joined: 12-October 09

Re: Tooltip not working in DOM elements

Posted 02 February 2012 - 06:18 AM

I've got this working in Opera & Firefox now, but not IE :@

I removed all the calls to ".tooltip" from the document ready, stuck them in another function called "applyTT", and just called that from document ready and from the AJAX call and it works perfectly...all except for bloody Internet Explorer, where it either doesn't work at all, or comes up with a tooltip saying "undefined".

Grr

This post has been edited by Duckington: 02 February 2012 - 06:20 AM

Was This Post Helpful? 0
  • +
  • -

#3 Duckington  Icon User is offline

  • D.I.C Addict

Reputation: 170
  • View blog
  • Posts: 608
  • Joined: 12-October 09

Re: Tooltip not working in DOM elements

Posted 02 February 2012 - 06:27 AM

Okay, it seems to be that the div isn't being created at all in IE. Something it doesn't like about insertBefore.

Hmm.
Was This Post Helpful? 0
  • +
  • -

#4 e_i_pi  Icon User is offline

  • = -1
  • member icon

Reputation: 799
  • View blog
  • Posts: 1,681
  • Joined: 30-January 09

Re: Tooltip not working in DOM elements

Posted 05 February 2012 - 05:19 AM

Okay, a couple of things here. First off, the tooltips.

There is a concept in jQuery called live binding. With live binding, you can attach an event handler to, say, a specific CSS class, and any newly created elements that have that class pick up the event handler automatically. This used to be achieved with the function .live(), but as of 1.7.1, event handlers are always picked up, as binding occurs through the .on() function.

Now, the reason why your tooltips aren't showing up is because they aren't live bound. You can emulate live bound tooltips by using the following code:
$('mySelector').live('mouseover', function() {
  $(this).tooltip({
    // insert your parameters here
  });
});


As of 1.7.1 though, the correct way to achieve this is as I said before, with the .on() function:
$('mySelector').on('mouseover', function() {
  $(this).tooltip({
    // insert your parameters here
  });
});


I haven't tested this with your code, but I would do a test run on one of those tooltip code sections you have, and see if it works. I imagine it would.

Secondly, the IE problem. Browser problems with jQuery are usually remedied by upgrading your jQuery. There is a known issue with IE8 in jQuery 1.3.2 using .insertBefore(). If you type in "jquery insertbefore ie8" in Google, you'll find a swathe of links relating to this. Now, I won't assume that you're using jQuery 1.3.2, but you may be using a version of jQuery that has incompatibility issues with the version of IE you are testing on. If upgrading your jQuery library doesn't work, can you post the versions of jQuery and IE you are using?

This post has been edited by e_i_pi: 05 February 2012 - 05:19 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1