Skip to content

Instantly share code, notes, and snippets.

@zrod
Created May 13, 2012 17:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zrod/2689396 to your computer and use it in GitHub Desktop.
Save zrod/2689396 to your computer and use it in GitHub Desktop.
ttpopover.js - A small (and yet in dev) jQuery popover plugin
/**
* ttpopover.js v0.2
* TT PopOver plugin
* @author Rodrigo Z. Arthuso
*
* Requires jQuery 1.7+, mustache.js 0.5+
*
* @TODO Review update position on resize
*/
( function( factory ) {
if ( typeof define === 'function' && define.amd ) {
define( ['jquery'], factory );
} else {
factory( jQuery );
}
} ( function ( $ ) {
"use strict";
var TTpopover = function ( element, options ) {
this.options = options;
this.rendered = false;
this.init( element );
};
TTpopover.prototype = {
init: function ( element ) {
var content,
popover,
triggerOn = this.options.trigger === 'hover' ? 'mouseover.ttpopover' : 'click.ttpopover',
triggerOff = this.options.trigger === 'hover' ? 'mouseout.ttpopover' : 'click.ttpopover';
// Register element
this.element = $( element );
// Set content
if ( this.options.content !== '' ) {
content = ( this.options.content instanceof window.jQuery ) ? this.options.content : $( this.options.content );
popover = $( '<div class="tt-popover"><div class="arrow"></div></div>' ).append( content.html() );
} else {
// Build content from data-* content
content = getContentFromDataAttr( this.element );
popover = $( Mustache.render( this.options.layout, content ) );
}
// Add custom classes to popover object
popover.addClass( this.options.customClass );
// Register popover object
this.popover = popover;
// Register position
this.position = this.options.position;
// Register trigger effect speed
this.effectSpeed = getTriggerEffectSpeed( this.options.effectSpeed );
// Watch trigger events
if ( this.options.trigger === 'click' ) {
this.popover.bind( triggerOn, function( e ) {
if ( e.target.tagName.toLowerCase() !== 'a' ) {
e.stopPropagation();
}
}
);
this.element.on( triggerOn,
$.proxy( function( e ) {
e.stopPropagation();
if ( !this.popover.is( ':visible' ) ) this.show();
else this.hide();
}, this )
);
// Hide popover when clicking off its limits
if ( this.options.hideOnOffClick ) {
$( 'body' ).not( this.popover ).on( triggerOff,
$.proxy( function() {
if ( this.popover.is( ':visible' ) ) this.hide();
}, this )
);
}
} else {
this.element.on( triggerOn, $.proxy( this.show, this ) );
this.element.on( triggerOff, $.proxy( this.hide, this ) );
}
// Re-position element on window resize
$( window ).resize(
$.proxy( function() {
this.hide(); //this.show();
}, this)
);
},
show: function () {
// Append popover to body (in case it hasn't already)
if ( !this.rendered ) {
$( 'body' ).append( this.popover );
// Do we have a callback?
if ( typeof( this.options.onRender ) === 'function' ) {
this.options.onRender.call( this );
}
this.rendered = true;
}
this.hideAll();
this.updatePosition();
if ( this.options.effect === 'fade' ) {
this.popover.stop( false, true ).fadeIn( this.effectSpeed );
} else {
this.popover.show();
}
// Add active class to element
if ( this.options.triggerActiveClass !== '' ) {
this.element.addClass( 'tt-popoverTrigger' + ' ' + this.options.triggerActiveClass );
}
},
hide: function () {
if ( this.options.effect === 'fade' ) {
this.popover.stop( false, true ).fadeOut( this.effectSpeed );
} else {
this.popover.hide();
}
// Remove active class from element
if ( this.options.triggerActiveClass !== '' ) {
this.element.removeClass( this.options.triggerActiveClass );
}
},
hideAll: function() {
var triggerClass = this.options.triggerActiveClass;
if ( triggerClass !== '' ) {
$( '.tt-popoverTrigger.' + triggerClass ).removeClass( triggerClass );
}
$( '.tt-popover' ).hide();
},
updatePosition: function() {
var popoverPosition;
this.popover.css({
display: 'block',
top: 0,
left: 0
});
popoverPosition = calcPosition( this.element, this.popover, this.position );
// Update position in case an 'auto' class is present
this.position = popoverPosition.position;
this.popover.css({
top: popoverPosition.top,
left: popoverPosition.left,
display: 'none'
});
}
};
// Private methods
/**
* getContentFromDataAttr
* @param el jQuery Object
* @return Object
*/
function getContentFromDataAttr( el ) {
var elData = el.data();
return {
title: elData.title || '',
content: elData.content || ''
};
}
/**
* getTriggerEffectSpeed
* @param speed Mixed
* @return mixed
*/
function getTriggerEffectSpeed( speed ) {
return (
speed !== '' && (
typeof( speed === 'number' ) ||
typeof( speed === 'string' )
)
) ? speed : 400;
}
/**
* calcPosition
* @param el Object
* @param popover Object
* @param position String
* @return pos Object
*/
function calcPosition( el, popover, position ) {
var elOffset = el.offset(),
fixed = popover.hasClass( 'fixed' ),
autoPosition = popover.hasClass( 'auto' ),
windowScrollTop = $( window ).scrollTop(),
windowHeight,
elScrollTop = ( windowScrollTop > elOffset.top ) ? windowScrollTop - elOffset.top : elOffset.top - windowScrollTop,
pos;
if ( autoPosition ) {
windowHeight = $( window ).height();
if ( position === 'top' || position === 'bottom' ) {
popover.removeClass( position );
position = ( ( windowHeight - elScrollTop ) > popover.outerHeight() ) ? 'bottom' : 'top';
}
}
switch ( position ) {
case 'top':
pos = { top: ( elOffset.top - popover.outerHeight() ), left: elOffset.left - ( popover.outerWidth() / 2 - el.outerWidth() / 2 ) };
break;
case 'bottom':
pos = { top: ( elOffset.top + el.outerHeight() ), left: elOffset.left - ( popover.outerWidth() / 2 - el.outerWidth() / 2 ) };
break;
case 'right':
pos = { top: elOffset.top, left: ( elOffset.left + el.outerWidth() ) };
break;
case 'left':
pos = { top: elOffset.top, left: ( elOffset.left - popover.outerWidth() ) };
break;
}
if ( fixed ) {
pos.top = elScrollTop;
popover.css( 'position', 'fixed' );
}
popover.addClass( position );
pos.position = position;
return pos;
}
// Defaults
TTpopover.defaults = {
trigger: 'click', // [click, hover]
position: 'left', // [top, right, bottom, left]
effect: '', // [fade]
effectSpeed: '', // [slow, fast, [INT]]
hideOnOffClick: true,
content: '',
customClass: '',
triggerActiveClass: 'active',
layout: '<div class="tt-popover"><div class="arrow"></div><div class="tt-popoverTitle">{{title}}</div><div class="tt-popoverContent">{{content}}</div></div>',
onRender: '' // callback fn to run after appending popover to document
};
// jQuery plugin definition
$.fn.ttpopover = function ( opt ) {
var options = $.extend( {}, TTpopover.defaults, opt );
return this.each( function() {
new TTpopover( this, options );
});
};
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment