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.

New Topic/Question
Reply



MultiQuote



|