1
\$\begingroup\$

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 );
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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 the var definition as $(this).wrapInner('<span />'); Also, you use === instead of == to check if "ajax" is an object. May I ask why? \$\endgroup\$ – user1032531 Mar 19 '13 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 Mar 19 '13 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 Mar 19 '13 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 Mar 19 '13 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 Mar 19 '13 at 14:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.