Skip to content

Instantly share code, notes, and snippets.

@brianfeister
Last active December 16, 2015 02:49
Show Gist options
  • Save brianfeister/5365416 to your computer and use it in GitHub Desktop.
Save brianfeister/5365416 to your computer and use it in GitHub Desktop.
Constrained popover to extend Bootstrap popover and make it intelligently switch orientation based on relative position of popup to container
if ( ! $.fn.constrained_popover ) {
// Constrained popover allows you to pass any DOM element in jQuery syntax and get a
// Bootstrap popover that changes orientation based on it's proximity to the container bounds
/* CONSTRAINED_POPOVER PUBLIC CLASS DEFINITION
* =========================================== */
var ConstrainedPopover = function ( element, options ) {
this.init('constrained_popover', element, options )
};
/* NOTE: CONSTRAINED_POPOVER EXTENDS BOOTSTRAP-POPOVER.js
========================================== */
ConstrainedPopover.prototype = $.extend( {}, $.fn.popover.Constructor.prototype, {
constructor: ConstrainedPopover
, show: function () {
var $tip
, inside
, pos
, newPos
, actualWidth
, actualHeight
, placement
, tp
, finalPos = {}
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
inside = /in/.test(placement)
$tip
.remove()
.css({ top: 0, left: 0, display: 'block' })
.appendTo(inside ? this.$element : document.body)
pos = this.getPosition(inside)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (inside ? placement.split(' ')[1] : placement) {
case 'left':
newPos = this.defineBounds( pos )
if ( typeof newPos.top === "undefined" ) {
finalPos["top"] = pos.top + pos.height / 2 - actualHeight / 2
} else {
finalPos["top"] = newPos.top - actualHeight / 2
}
if ( typeof newPos.left === "undefined" ) {
finalPos["left"] = pos.left - actualWidth
} else {
finalPos["left"] = newPos.left - actualWidth
}
tp = { top: finalPos.top , left: finalPos.left }
break
case 'right':
newPos = this.defineBounds( pos )
if ( typeof newPos.top === "undefined" ) {
finalPos["top"] = pos.top + pos.height / 2 - actualHeight / 2
} else {
finalPos["top"] = newPos.top - actualHeight / 2
}
if ( typeof newPos.left === "undefined" ) {
finalPos["left"] = pos.left + pos.width
} else {
finalPos["left"] = newPos.left + pos.width
}
tp = { top: finalPos.top , left: finalPos.left }
break
}
$tip
.css(tp)
.addClass(placement)
.addClass('in')
}
}
, defineBounds: function ( pos ) {
var container
, containerOffset
, boundTop
, boundLeft
, boundBottom
, boundRight
, newPos = {}
if ( $(this.options.container).length !== 0 ) {
// verify there is no special "inner-container" with checkMultiContainer()
container = this.checkMultiContainer()
containerOffset = container.offset()
boundTop = containerOffset.top
boundLeft = containerOffset.left
boundBottom = boundTop + container.height()
boundRight = boundLeft + container.width()
// Constrain y-axis overflow
if ( pos.top + ( pos.height / 2 ) < boundTop ) {
newPos["top"] = boundTop
}
if ( pos.top + ( pos.height / 2 ) > boundBottom ) {
newPos["top"] = boundBottom
}
// Constrain x-axis overflow
if ( pos.left - ( pos.width / 2 ) < boundLeft ) {
newPos["left"] = boundLeft
}
if ( pos.left - ( pos.width / 2 ) > boundRight ) {
newPos["left"] = boundRight
}
return newPos
}
else {
return false
}
}
, checkMultiContainer: function () {
var container
, containerNum
container = $( this.options.container )
if ( container.length ) {
if ( container.length > 1 ) {
container = this.$element.closest( container )
}
return container
} else {
return
}
}
})
/* CONSTRAINED_POPOVER PLUGIN DEFINITION
* ===================================== */
$.fn.constrained_popover = function ( option ) {
return this.each( function () {
var $this = $(this)
, data = $this.data('constrained_popover')
, options = typeof option == 'object' && option
if (!data) $this.data('constrained_popover', (data = new ConstrainedPopover(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.constrained_popover.Constructor = ConstrainedPopover
$.fn.constrained_popover.defaults = $.extend({} , $.fn.popover.defaults, {
container: ''
, content: this.options
})
} // END: CONSTRAINED_POPOVER
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment