This plugin displays a tooltip popup with the data obtained via Ajax. I am sure there are better ones out there, but my objective is to learn how to correctly build a plugin, not find the best one available. I would appreciate any comments, suggestions, criticism from a best practices and design pattern usage perspective.
A live demo is located here.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>screenshot</title>
<script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script>
<script src="jquery.ajaxTip.js" type="text/javascript"></script>
<style type="text/css">
.myElement{margin:100px;}
.ajaxToolActive{color:blue;}
.myAjaxTip {
border:1px solid #CECECE;
background:white;
padding:10px;
display:none;
color:black;
font-size:11px;-moz-border-radius:4px;
box-shadow: 3px 1px 6px #505050;
-khtml-border-radius:4px;
-webkit-border-radius:4px;
border-radius:4px;
}
</style>
<script type="text/javascript">
$(function(){
$('.myElement').ajaxTip({
display: function(d){return '<p>'+d.name+'</p><p>'+d.address+'</p><p>'+d.city+', '+d.state+'</p>';},
getData:function(){return {id:this.data('id')}},
'class':'myAjaxTip'
});
$('.destroy').click(function(){$('.myElement').ajaxTip('destroy');});
});
</script>
</head>
<body>
<p class="myElement" data-id="1" title="ajaxTip Popup">John Doe</p>
<p class="myElement" data-id="2" title="ajaxTip Popup">Jane Doe</p>
<p class="myElement" data-id="3" title="ajaxTip Popup">Baby Doe</p>
<p class="destroy">Destroy</p>
</body>
</html>
/*
* jQuery ajaxTip
* Copyright 2013 Michael Reed
* Dual licensed under the MIT and GPL licenses.
*/
(function( $ ){
var methods = {
init : function( options ) {
// Create some defaults, extending them with any options that were provided
var settings = $.extend({
'url' : 'getAjaxTip.php', //To include extra data sent to the server, included it in the url
'class' : '', //Class to be added to tooltip (along with class standardAjaxTip)
'mouseMove': true, //Whether to move tooltip with mouse
'speed' : 'fast', //fadeIn speed
'delay' : 250, //milliseconds to delay before requesting data from server
'xOffset' : 20,
'yOffset' : 10,
'dataType' : 'json', //Returned data. Options are json, text, etc
'getData' : function(){return {}}, //Use to set additional data to the server
'display' : function(data){ //User function must include function(data) {... return string}
var string='';
for (var key in data) {string+='<p>'+data[key]+'</p>';}
return string;
}
}, options || {}); //Just in case user doesn't provide options
return this.each(function(){
var showing,title,timeoutID,ajax,$t=$(this).wrapInner('<span />'),ajaxTip;
$t.children('span').hover(function(e) {
if(!showing){
title = $t.attr('title');$t.attr('title','');//Prevent title from being displayed,and save for later to put back
timeoutID=window.setTimeout(function() {
ajax=$.get( settings.url,settings.getData.call($t),function(data){
ajaxTip=$('<div />')
.addClass('standardAjaxTip '+settings.class)
.html(((title != '')?'<h3>'+title+'</h3>':'')+settings.display(data))
.css("top",(e.pageY - settings.yOffset) + "px")
.css("left",(e.pageX + settings.xOffset) + "px")
.css("position","absolute")
.appendTo('body').fadeIn(settings.speed);
showing = true;
$t.addClass('ajaxToolActive');
}, settings.dataType);
},settings.delay); //Delay before requesting data from server
}
},
function()
{
//When not hover
if (typeof ajax == 'object') {ajax.abort();}
window.clearTimeout(timeoutID);
$t.attr('title',title);
$t.removeClass('ajaxToolActive');
if(showing){ajaxTip.remove();}
showing = false;
});
$t.mousemove(function(e) {
if(settings.mouseMove && showing) {ajaxTip.css("top",(e.pageY - settings.yOffset) + "px").css("left",(e.pageX + settings.xOffset) + "px");}
});
});
},
//Add additional methods as needed
destroy : function() {
//console.log('destroy');
return this.each(function(){
var $e = $(this);
$e.html($e.children('span').html());
})
},
};
$.fn.ajaxTip = function(method) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.ajaxTip' );
}
};
})( jQuery );
1 Answer 1
Here's the code mostly the same with some changes to style and comments:
(function($){
var defaults = {
'url' : 'getAjaxTip.php', // The url used to get the tooltip data.
'class' : '', // Css class(es) to add to tooltip (along with standardAjaxTip).
'mouseMove': true, // A flag indicating whether to move tooltip with mouse.
'speed' : 'fast', // The speed at which to fade in the tool tip.
'delay' : 250, // Delay (in ms) before requesting data from server.
'xOffset' : 20,
'yOffset' : 10,
'dataType' : 'json',
'getData' : function () {
return {};
},
// A function to transform the data from the server into an html fragment.
'display' : function(data) {
var htmlString = '';
$.each(data, function (key, val) {
htmlString += '<p>' + val + '</p>';
});
return htmlString;
}
};
var methods = {
init : function (options) {
// Create settings using the defaults extended with any options provided.
var settings = $.extend(defaults, options || {});
return this.each(function () {
var title,
timeoutID,
ajax,
$t,
ajaxTip;
// Wrap the content of the current element in a span.
$t = $(this).wrapInner('<span />');
$t.children('span').hover(function(e) {
if(!$t.hasClass('ajaxToolActive')) {
title = $t.attr('title');
$t.attr('title',''); // Remove the title so that it doesn't show on hover.
timeoutID = window.setTimeout(function () {
ajax = $.get(settings.url, settings.getData.call($t), function (data) {
// Create a div to be the tooltip pop up, add the styling as well as
// the html (from the display function) to it and then fade the element in
// using the speed specified in the settings.
ajaxTip = $('<div />')
.addClass('standardAjaxTip ' + settings['class'])
.html(((title !== '') ? '<h3>' + title + '</h3>' : '') + settings.display(data))
.css('top', (e.pageY - settings.yOffset) + 'px')
.css('left', (e.pageX + settings.xOffset) + 'px')
.css('position', 'absolute')
.appendTo('body')
.fadeIn(settings.speed);
$t.addClass('ajaxToolActive');
},
settings.dataType);
}, settings.delay);
}
},
function () {
// User is no longer hovering so cancel the call to the server and hide the tooltip.
if (typeof ajax === 'object') {
ajax.abort();
}
window.clearTimeout(timeoutID);
$t.attr('title', title);
if ($t.hasClass('ajaxToolActive')) {
ajaxTip.remove();
$t.removeClass('ajaxToolActive');
}
});
$t.mousemove(function (e) {
if (settings.mouseMove && $t.hasClass('ajaxToolActive')) {
ajaxTip.css('top', (e.pageY - settings.yOffset) + 'px')
.css('left', (e.pageX + settings.xOffset) + 'px');
}
});
});
},
destroy : function () {
return this.each(function () {
var $e = $(this);
$e.html($e.children('span').html());
});
}
};
$.fn.ajaxTip = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.ajaxTip');
}
};
}(jQuery));
I think it would also be a good idea to keep lines to 80 characters. I also removed the showing variable and checked to see if the element had the active class instead. The other main thing I change was settings.class to settings['class'] as class is a future reserved word.
-
\$\begingroup\$ Thanks RobH! I like your use of using the class instead of variable "showing" as well as creating a new variable "defaults". I see you didn't define
$t
in thevar
definition as$(this).wrapInner('<span />');
Also, you use===
instead of==
to check if "ajax" is an object. May I ask why? \$\endgroup\$user1032531– user10325312013年03月19日 13:16:06 +00:00Commented Mar 19, 2013 at 13:16 -
\$\begingroup\$ @user1032531 I decided to split out the definition of $t and the assigning of it to leave a space for a comment as it wasn't obvious to me why you are wrapping it first (I meant to ask!). In JavaScript == and != use type coercion which isn't normally what you want to do so it's good to get into the habit of using === and !== which don't convert types while checking equality. There is a great SO answer on the topic here \$\endgroup\$RobH– RobH2013年03月19日 13:24:17 +00:00Commented Mar 19, 2013 at 13:24
-
\$\begingroup\$ Thanks again Rob, I really appreciate your time. I am 100% self taught, and while resources like SO definitely provide me with most of the knowledge I need, having someone look over the final results and critique is is invaluable. It would be nice to even get a grade! Maybe A to F for useability, best practices, etc. By the way, you think I might get a B? \$\endgroup\$user1032531– user10325312013年03月19日 13:42:29 +00:00Commented Mar 19, 2013 at 13:42
-
\$\begingroup\$ PS. I wrapped the
<DIV>
with a<SPAN>
so that hover is only active when over the text and not the entire row of the block element. Maybe a hack, but didn't know a better way. Do you have a better suggestion? \$\endgroup\$user1032531– user10325312013年03月19日 13:44:13 +00:00Commented Mar 19, 2013 at 13:44 -
\$\begingroup\$ @user1032531 I don't think it's a bad effort at all. As for the wrapping with a
<span>
- you could style your block elements to be a certain width, or use inline elements instead of your<p>
s. \$\endgroup\$RobH– RobH2013年03月19日 14:05:16 +00:00Commented Mar 19, 2013 at 14:05