Page 1 of 1

"Tipped": jQuery tooltip plugin

Posted: Fri Mar 12, 2010 4:13 pm
by pickle
Update 5: Created an official jQuery plugin project.
Update 4: The boss gave me the green light to release this open-source. The project page is here
Update 3: Reposition tooltip at top left before width calculation for repositioning done. This prevents inline elements from being squished.
Update 2: Fixed the showing/hiding of the "Close" button if there are tips with both "hover" and "click" mode
Update: Added ability to turn off the default tooltip that appears when an element has a title. Also added better creation of #tipped

Hi all,

Just finished version 2 of this baby & thought I'd throw it to the sharks. I created this plugin because nothing I found allowed me the flexibility I needed.

Features:
  • Tooltip can be triggered by hovering, or made sticky by clicking
  • Tooltip content can be provided as a string, pulled from the 'title' attribute, a jQuery object, returned from a callback function, or requested via AJAX
  • AJAX requests can be cached *this was one of the main reasons I made my own plugin
  • Tooltips can show a loading image while waiting for the AJAX request to be returned *this was the other main reason
  • Plugin ensures the tip appears fully on screen
Todo:
If the content of the tip is taller or wider than the viewport, restrict the dimensions of the tip to the viewport, and provide scrollbars

Usage:

Code: Select all

<input type = "hidden" name = "secret" value = "something" /><img src = "/path/to/some/image.png" alt = "Image" id = "tipMe" title = "I'm being tipped" />

Code: Select all

#tipped{
  /* Add whatever styles you want to make the tip look good, but these 2 properties must be defined */  
  position:absolute;
  display:none;
}
#tipped-closer-wrapper{ /* This is the id given to the <div/> around close button */ }
#tipped-closer{ /* This is the id given to the <span/> around the provided close text/html */ }

Code: Select all

//Can be as simple as:
$("#tipMe").tipped();//will provide a hover triggered tooltip that uses the 'title' attribute 

//or as useful as:
$("#tipMe").tipped({
  ajaxType:'GET',
  closer: 'Click me to close the tooltip',
  mode: 'click',
  params:function($trigger){
    return {  secret:$trigger.siblings('input:first').val()};  
  },
  source:'url',
  throbber:'/path/to/loading/graphic.gif',
  url:'encodeSecret.php'});
The last example will:
  1. Show the tip when #tipMe is clicked
  2. Show the tooltip with "graphic.gif" displayed
  3. Generate an AJAX request to "encodeSecret.php?secret=something"
  4. Show the result once its returned
  5. Provide a link labelled "Click me to close the tooltip" which will hide the tip when clicked

Code comes in at 7.97K/ 2.29K (Google Closure compiler)

Code: Select all

/******
 * Tipped: A tooltip plugin for jQuery
 * http://www.augustana.ualberta.ca/tls/help/foss/tipped
 *
 * Copyright 2010, University of Alberta
 *
 * Tipped is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Tipped is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Tipped.  If not, see <http://www.gnu.org/copyleft/lesser.html>
 *
 * v1.3.3:	- Became a good jQuery citizen and return the jQuery object from tipped() so it supports chaining
 *			- Fixed a bug that emptied out the title stored in data(), if tipped() is called
 *			  on an element twice
 * v1.3.2:	Fixed 'title' based tips that were trying to show the title from the attribute after it was emptied out
 * v1.3.1:	Did some stuff
 * v1.3:	Reposition tooltip at top left before width calculation for repositioning done.  This
 *			prevents inline elements from being squished.
 * v1.2:	Fixed showing/hiding of the "Close" button if there are tips with both "hover" and "click" mode
 * v1.1: 	- Added turning off of default tooltip that appears when an elelment has a title
 *			- #tipped element is now created explicitely as an window variable - fixes a problem with Safari
 * v1.0: 	Initial release
 */
(function($) {
	/******
	/*	Options
		
			ajaxType:	The type of HTTP request to make.
				Possible values: Anything $.ajax accepts (usually 'GET' or 'POST')
				Default: 'POST'
			
			cache:		Whether or not to cache AJAX requests.  Cache is based on URL, not URL + data, so if 
						you are making multiple requests to the same URL with different data, turn off cache
				Default: false
			
			closer:		The HTML to display when a tip is to be manually closed (ie: when triggered by a click).  
						All text in 'closer' will be injected inside another element that has the close listener
				Default: 'Close'
			
			marginX:	The pixels to the right of the element that the tip should appear.  This amount will be
						overridden if necessary to ensure the entire tip shows on the screen.
				Possible values: Any integer.  Negative numbers will position the tip to the left of the right
								 edge of the triggering element
				Default: '10'
				
			marginY:	The pixels to the bottom of the element that the tip should appear.  This amount will be
						overridden if necessary to ensure the entire tip shows on the screen.
				Possible values: Any integer.  Negative numbers will position the tip above of the bottom
								 edge of the triggering element
				Default: '10'
				
			mode:		The type of tip to make.  'Hover' shows and hides on hover, 'Click' is triggered with a
						click and requires clicking of the closer to go away
				Possible values: 'hover', 'click'
				Default: 'hover'
				
			params:		An object representing the parameters to send along with an AJAX request as 'data'
				Possible values:
					A callback: Data passed will be the object returned from this function.  Function will be passed
								a jQuery object representing the triggering element
					An object: Will be used as the data
				Default: {}
				
			source:		The source of the value to display.
				Possible values: 
					'title':	Value to display will be pulled from the 'title' attribute of the triggering element
					A callback: Value to display will be returned from the callback function.  Function will be passed
								a jQuery object representing the triggering element
					'url':		An AJAX request will be made to the address specified by the 'url' option
					Any other string:	Will be displayed
				Default: 'title'
				
			throbber:	The URL to the image to display while the AJAX request is being sent.  If blank, no throbber
						will be shown.
				Default: ''
				
			url:		The web address to make the AJAX request to.  Unused if 'source' is not 'url'
	*/
	
	var defaults = {
		ajaxType:'POST',
		cache:false,
		cached:{},
		closer:'Close',
		marginX:10,
		marginY:10,
		mode:'hover',
		params:{},
		source:'title',
		throbber:'',
		url:''
	};
	
	//create single tooltip
	window.$tip = {};
	window.$tip_content = {}

	$(document).ready(function(){
		$tip = $("#tipped").length ? $("#tipped") : $('<div id = "tipped"><div id = "tipped_content"></div></div>').appendTo(document.body).data('showing',false);
		$tip_content = $("#tipped_content");
	});

	$.fn.tipped = function(settings){
		this.each(function(i){
			
			$target = $(this);//shortcut
			
			//store settings
			settings = $.extend({},defaults,settings);
			$target.data('tipped',{settings:settings});			
			
			//2 modes act differently
			if(settings.mode == 'hover')
				$target
					.mouseover(function(){
						$.fn.tipped.showTip($(this));
					})
					.mouseout(function(){
						$.fn.tipped.hideTip($(this));
					});
			else if(settings.mode == 'click')
			{
				//add closer if necessary
				if($("#tipped-closer").length == 0)
					$tip.append('<div id = "tipped-closer-wrapper"><span id = "tipped-closer">'+settings.closer+'</span>');
					
				$target.click(function(){
					$this = $(this);
					$.fn.tipped.showTip($this);
					$("#tipped-closer").click(function(){
						$.fn.tipped.hideTip($this);
					});
				});
			}	
		});
		
		return this;
	};
	
	/**
	 * Function: showTip()
	 * Purpose: To initiate the showing of a tip.
	 * Parameters: $target: a jQuery object that has had a tip bound to it.  Tipped uses
	 *                      the settings associated with the $target to determine what to display
	 */
	$.fn.tipped.showTip = function($target)
	{
		//shortcuts
		var settings = $target.data('tipped').settings;
		var cached = $tip.data('cached');

		//manage the closer
		if(settings.mode != 'click')
			$("#tipped-closer-wrapper").hide();
		else
			$("#tipped-closer-wrapper").show();

		//hide the original title
		if($target.data('tipped').title === undefined)
		{
			$target.data('tipped',$.extend($target.data('tipped'),{title:$target.attr('title')}));
			$target.attr('title','');
		}

		//AJAX
		if(settings.source === 'url')
		{
			//if we're not caching, retrieve the value
			if(!settings.cache || cached === undefined || cached[settings.url] === undefined)
			{
				//set parameters
				var data = {};
				if(typeof settings.params == 'function')
					data = settings.params($target);
				else if(typeof settings.params == 'object')
					data = settings.params;
					
				$.ajax({
					beforeSend:function(){
						show($target,'<img src = '+settings.throbber+' alt = "Loading..." />');
					},
					data:data,
					error:function(){
						show($target,'Unable to retrieve contents');
					},
					success:function(display){
						if($tip.data('showing'))
							show($target,display);
						
						//cache results if necessary
						if(settings.cache)
						{
							var newCache = new Object;
							newCache[settings.url] = display;
							cached = $.extend(cached,newCache);
							$tip.data('cached',cached);
						}
					},
					type:settings.ajaxType,
					url:settings.url
				});
				return;
			}
			//otherwise, show the cached copy
			else
			{
				show($target,cached[settings.url]);
				return;
			}
		}
				
		
		var value = '';
		
		//'title' attribute
		if(settings.source === 'title')
			value = $target.data('tipped').title;
		
		//any other string
		else if(typeof settings.source == 'string')
			value = settings.source;
			
		//custom function
		else if(typeof settings.source == 'function')
			value = settings.source($target);
		
		//jQuery object
		else if(typeof settings.source == 'object')
			value = settings.source.html();
		
		show($target,value);
	}
	
	/*
	 * Function: hideTip()
	 * Purpose: To hide the tip
	 * Parameters: $target:	a jQuery object representing the element that triggered the tip
	 */
	$.fn.tipped.hideTip = function($target)
	{
		$target.attr('title',$target.data('tipped').title);
		$tip.data('showing',false).data('original','').hide();
		$tip_content.html('');
	}
	
	
	/*
	 * Function: getTrigger()
	 * Purpose: To provide access to the element that triggered the tip.  Useful for 
	 *          clicked tips that need to know who triggered them
	 *
	 * Access with: $.getTrigger()
	 */
	$.extend({
		getTrigger:function(){
			return $tip.data('original');
		}
	});

	/*
	 * Function: show()
	 * Purpose: To actually show the tip
	 * Parameters: $target: The element (wrapped in a jQuery object) that triggered the showing of this tip
	 *			   value: The HTML to place into the tip
 	 *
	 * Note: This function is private
	 */
	function show($target,value)
	{
		$tip_content.html(value);
		setPosition($target)
		$tip.data('showing',true).data('original',$target).show();
	}
	
	
	/*
	 * Function: setPosition()
	 * Purpose: To set the position of the tip.  This function is called after the content of the tip
	 *          is set, allowing the function to make a dynamic decision about the position of the tip
	 *			
	 *			The tip is always displayed fully on the screen & will be moved to ensure that.
	 * Parameters: $elem:	a jQuery object representing the element relative to which the tip is to be positioned.
	 *
	 * Note: This function is private
	 */
	function setPosition($elem)
	{
		var settings = $elem.data('tipped').settings;		

		//position tip in the top left corner, so full, proper width gets calculated
		$tip.css({left:0,top:0});

		//determine element position on screen
		var elemPos = $elem.offset();
		var posX = elemPos.left + $elem.outerWidth() + settings.marginX;
		var posY = elemPos.top + $elem.outerHeight() + settings.marginY;
		
		//adjust to ensure tip is inside viewable screen
		var right = posX + $tip.outerWidth();
		var bottom = posY + $tip.outerHeight();
		
		var windowWidth = $(window).width() + $(window).scrollLeft()-5;
		var windowHeight = $(window).height() + $(window).scrollTop()-5;
		
		posX = (right > windowWidth) ? posX - (right - windowWidth) : posX;
		posY = (bottom > windowHeight) ? posY - (bottom - windowHeight) : posY

		$tip.css({ left: posX, top: posY });
	}
})(jQuery);
Let me know what you think.

Re: "Tipped": jQuery tooltip plugin

Posted: Fri Mar 12, 2010 5:15 pm
by s.dot
Hey, I have been looking for something like this! I found all of the other tooltip plugins not to my liking, with only one that was suitable. Do you have a working demo somewhere?

Re: "Tipped": jQuery tooltip plugin

Posted: Mon Mar 15, 2010 10:22 am
by pickle
Not really - but I can show examples here if you want. It's pretty trivial to implement if you want to try it yourself.

Re: "Tipped": jQuery tooltip plugin

Posted: Mon Apr 12, 2010 3:13 am
by s.dot
Loving this, pickle. :)

I am implementing it on a forum board to give a "topic preview" when the topic title is hovered over. A couple of Q's..

1) When I hover the topic title, the tipped div shows as expected, but the title attribute pops up like a normal browser title hover. It's the same content, so it looks kind of silly having the tipped and the browser show the same title. I'd like to get rid of the browser popping up the title information. Possible?

2) Positioning. How does it determine where to pop up at, currently? Is it movable. I tried setting the top/left positioning with no success.

Here's the html I'm using it on:

Code: Select all

<a class="ftopic" href="http://localhost/site/forums.php?t=1" title="And I am in the general chat forum.  and i cover multiple lines  do you  ..">I am a general topic</a>
here's the css:

Code: Select all

#tipped{ 
  position:absolute;
  display:none;
  background-color: #fff;
  border: solid 1px #e6e6e6;
  padding: 3px;
}
#tipped-closer-wrapper{ /* This is the id given to the <div/> around close button */ }
#tipped-closer{ /* comment */ }

Re: "Tipped": jQuery tooltip plugin

Posted: Mon Apr 12, 2010 9:47 am
by pickle
1) Go to the project page on Google code: http://code.google.com/p/tipped/ to download the latest version. The 'title' problem is fixed there.
2) The tip appears at the bottom right corner of the triggering element. From that point, it is offset by the marginX and marginY options, which default to 10 pixels. This being done in Javascript, any positioning you apply in CSS will be ignored.

Re: "Tipped": jQuery tooltip plugin

Posted: Mon Apr 12, 2010 1:54 pm
by s.dot
1) I have jquery.tipped-1.4.js, which appears to be the latest one? (Maybe I'm missing it, your initial post says v2 is finished)
2) Perhaps a function or argument could be written to offset the position? Dunno how tricky this would be. I could edit the position myself (download full version and edit and then minify it) but this is the coding critique forum. :-D

Re: "Tipped": jQuery tooltip plugin

Posted: Mon Apr 12, 2010 2:49 pm
by pickle
1) What are you hovering over? Is there a URL I can look at? Are you using jQuery 1.4.2 (not sure how older versions would break it)?
2) How would you imagine this function working? Similar to the param option in that it accepts the triggering element, and returns absolute x & y co-ordinates?

Re: "Tipped": jQuery tooltip plugin

Posted: Tue Apr 13, 2010 3:08 am
by s.dot
1) PM'd you
2) I imagine just some param/function to set the absolute values of the position. Perhaps $(el).tipped({ xpos: x, ypos: y})? Or maybe some options to set to top/left top/middle center/center etc. Not sure how hard this would be with your ensuring the tip is fully on screen. Or maybe popup where the mouseover occurs (this would be ideal).

Re: "Tipped": jQuery tooltip plugin

Posted: Wed Apr 14, 2010 2:28 am
by s.dot
OK, noticed a couple of more things. Don't mean to harass you, really :D Just stating suggestions for how I would like to use it (perhaps someone else would like to use it the same way).

CSS width property for #tipped does not work, but max-width does.
Should width work?

How about a delay setting for the hover mode? Eg hover for x milliseconds before tipped pops up. For my use, it would be useful to see if the user is wanting to click the topic or waiting to read the preview. If they're just wanting to click, no need to show the preview. But if they hover, then I can give them what they want.

Re: "Tipped": jQuery tooltip plugin

Posted: Wed Apr 14, 2010 10:02 am
by pickle
The width gets reset every time the tip is shown. This is because only one DOM element is used for every tip. The width needs to be reset so the tip is only as wide as is necessary.

The delay is simple to add. I'll do so and roll out v1.5.

Re: "Tipped": jQuery tooltip plugin

Posted: Wed Apr 14, 2010 12:14 pm
by pickle
1.5 has been rolled out: http://code.google.com/p/tipped/