/* ==========================================================
* sco.tooltip.js
* http://github.com/terebentina/sco.js
* ==========================================================
* Copyright 2013 Dan Caragea.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
/*jshint laxcomma:true, sub:true, browser:true, jquery:true, smarttabs:true, eqeqeq:false */
;(function($, undefined) {
"use strict";
var pluginName = 'scojs_tooltip';
function Tooltip($trigger, options) {
this.options = $.extend({}, $.fn[pluginName].defaults, options);
this.$trigger = this.$target = $trigger;
this.leaveTimeout = null;
this.$tooltip = $('
').appendTo(this.options.appendTo).hide();
if (this.options.contentElem !== undefined && this.options.contentElem !== null) {
this.options.content = $(this.options.contentElem).html();
} else if (this.options.contentAttr !== undefined && this.options.contentAttr !== null) {
this.options.content = this.$trigger.attr(this.options.contentAttr);
}
if (this.$trigger && this.$trigger.attr('title')) {
this.$trigger.data('originalTitle', this.$trigger.attr('title'));
}
this.$tooltip.find('span').html(this.options.content);
if (this.options.cssclass != '') {
this.$tooltip.addClass(this.options.cssclass);
}
if (this.options.target !== undefined) {
this.$target = $(this.options.target);
}
if (this.options.hoverable) {
var self = this;
this.$tooltip.on('mouseenter.' + pluginName, $.proxy(this.do_mouseenter, self))
.on('mouseleave.' + pluginName, $.proxy(this.do_mouseleave, self))
.on('close.' + pluginName, $.proxy(this.hide, self));
}
}
$.extend(Tooltip.prototype, {
show: function(allowMirror) {
if (allowMirror === undefined) {
allowMirror = true;
}
this.$tooltip.removeClass('pos_w pos_e pos_n pos_s pos_nw pos_ne pos_se pos_sw pos_center').addClass('pos_' + this.options.position);
var targetBox = this.$target.offset()
,tooltipBox = {left: 0, top: 0, width: Math.floor(this.$tooltip.outerWidth()), height: Math.floor(this.$tooltip.outerHeight())}
,pointerBox = {left: 0, top: 0, width: Math.floor(this.$tooltip.find('.pointer').outerWidth()), height: Math.floor(this.$tooltip.find('.pointer').outerHeight())}
,docBox = {left: $(document).scrollLeft(), top: $(document).scrollTop(), width: $(window).width(), height: $(window).height()}
;
targetBox.left = Math.floor(targetBox.left);
targetBox.top = Math.floor(targetBox.top);
targetBox.width = Math.floor(this.$target.outerWidth());
targetBox.height = Math.floor(this.$target.outerHeight());
if (this.options.position === 'w') {
tooltipBox.left = targetBox.left - tooltipBox.width - pointerBox.width;
tooltipBox.top = targetBox.top + Math.floor((targetBox.height - tooltipBox.height) / 2);
pointerBox.left = tooltipBox.width;
pointerBox.top = Math.floor(targetBox.height / 2);
} else if (this.options.position === 'e') {
tooltipBox.left = targetBox.left + targetBox.width + pointerBox.width;
tooltipBox.top = targetBox.top + Math.floor((targetBox.height - tooltipBox.height) / 2);
pointerBox.left = -pointerBox.width;
pointerBox.top = Math.floor(tooltipBox.height / 2);
} else if (this.options.position === 'n') {
tooltipBox.left = targetBox.left - Math.floor((tooltipBox.width - targetBox.width) / 2);
tooltipBox.top = targetBox.top - tooltipBox.height - pointerBox.height;
pointerBox.left = Math.floor(tooltipBox.width / 2);
pointerBox.top = tooltipBox.height;
} else if (this.options.position === 's') {
tooltipBox.left = targetBox.left - Math.floor((tooltipBox.width - targetBox.width) / 2);
tooltipBox.top = targetBox.top + targetBox.height + pointerBox.height;
pointerBox.left = Math.floor(tooltipBox.width / 2);
pointerBox.top = -pointerBox.height;
} else if (this.options.position === 'nw') {
tooltipBox.left = targetBox.left - tooltipBox.width + pointerBox.width; // +pointerBox.width because pointer is under
tooltipBox.top = targetBox.top - tooltipBox.height - pointerBox.height;
pointerBox.left = tooltipBox.width - pointerBox.width;
pointerBox.top = tooltipBox.height;
} else if (this.options.position === 'ne') {
tooltipBox.left = targetBox.left + targetBox.width - pointerBox.width;
tooltipBox.top = targetBox.top - tooltipBox.height - pointerBox.height;
pointerBox.left = 1;
pointerBox.top = tooltipBox.height;
} else if (this.options.position === 'se') {
tooltipBox.left = targetBox.left + targetBox.width - pointerBox.width;
tooltipBox.top = targetBox.top + targetBox.height + pointerBox.height;
pointerBox.left = 1;
pointerBox.top = -pointerBox.height;
} else if (this.options.position === 'sw') {
tooltipBox.left = targetBox.left - tooltipBox.width + pointerBox.width;
tooltipBox.top = targetBox.top + targetBox.height + pointerBox.height;
pointerBox.left = tooltipBox.width - pointerBox.width;
pointerBox.top = -pointerBox.height;
} else if (this.options.position === 'center') {
tooltipBox.left = targetBox.left + Math.floor((targetBox.width - tooltipBox.width) / 2);
tooltipBox.top = targetBox.top + Math.floor((targetBox.height - tooltipBox.height) / 2);
allowMirror = false;
this.$tooltip.find('.pointer').hide();
}
// if the tooltip is out of bounds we first mirror its position
if (allowMirror) {
var newpos = this.options.position
,do_mirror = false;
if (tooltipBox.left < docBox.left) {
newpos = newpos.replace('w', 'e');
do_mirror = true;
} else if (tooltipBox.left + tooltipBox.width > docBox.left + docBox.width) {
newpos = newpos.replace('e', 'w');
do_mirror = true;
}
if (tooltipBox.top < docBox.top) {
newpos = newpos.replace('n', 's');
do_mirror = true;
} else if (tooltipBox.top + tooltipBox.height > docBox.top + docBox.height) {
newpos = newpos.replace('s', 'n');
do_mirror = true;
}
if (do_mirror) {
this.options.position = newpos;
this.show(false);
return this;
}
}
// if we're here, it's definitely after the mirroring or the position is center
// this part is for slightly moving the tooltip if it's still out of bounds
var pointer_left = null,
pointer_top = null;
if (tooltipBox.left < docBox.left) {
pointer_left = tooltipBox.left - docBox.left - pointerBox.width / 2;
tooltipBox.left = docBox.left;
} else if (tooltipBox.left + tooltipBox.width > docBox.left + docBox.width) {
pointer_left = tooltipBox.left - docBox.left - docBox.width + tooltipBox.width - pointerBox.width / 2;
tooltipBox.left = docBox.left + docBox.width - tooltipBox.width;
}
if (tooltipBox.top < docBox.top) {
pointer_top = tooltipBox.top - docBox.top - pointerBox.height / 2;
tooltipBox.top = docBox.top;
} else if (tooltipBox.top + tooltipBox.height > docBox.top + docBox.height) {
pointer_top = tooltipBox.top - docBox.top - docBox.height + tooltipBox.height - pointerBox.height / 2;
tooltipBox.top = docBox.top + docBox.height - tooltipBox.height;
}
this.$tooltip.css({left: tooltipBox.left, top: tooltipBox.top});
if (pointer_left !== null) {
this.$tooltip.find('.pointer').css('margin-left', pointer_left);
}
if (pointer_top !== null) {
this.$tooltip.find('.pointer').css('margin-top', '+=' + pointer_top);
}
this.$trigger.removeAttr('title');
this.$tooltip.show();
return this;
}
,hide: function() {
if (this.$trigger.data('originalTitle')) {
this.$trigger.attr('title', this.$trigger.data('originalTitle'));
}
if (typeof this.options.on_close == 'function') {
this.options.on_close.call(this);
}
this.$tooltip.hide();
}
,do_mouseenter: function() {
if (this.leaveTimeout !== null) {
clearTimeout(this.leaveTimeout);
this.leaveTimeout = null;
}
this.show();
}
,do_mouseleave: function() {
var self = this;
if (this.leaveTimeout !== null) {
clearTimeout(this.leaveTimeout);
this.leaveTimeout = null;
}
if (this.options.autoclose) {
this.leaveTimeout = setTimeout(function() {
clearTimeout(self.leaveTimeout);
self.leaveTimeout = null;
self.hide();
}, this.options.delay);
}
}
});
$.fn[pluginName] = function(options) {
var method = null
,first_run = false
;
if (typeof options == 'string') {
method = options;
}
return this.each(function() {
var obj;
if (!(obj = $.data(this, pluginName))) {
var $this = $(this)
,data = $this.data()
,opts
;
first_run = true;
if (typeof options === 'object') {
opts = $.extend({}, options, data);
} else {
opts = data;
}
obj = new Tooltip($this, opts);
$.data(this, pluginName, obj);
}
if (method) {
obj[method]();
} else if (first_run) {
$(this).on('mouseenter.' + pluginName, function() {
obj.do_mouseenter();
}).on('mouseleave.' + pluginName, function() {
obj.do_mouseleave();
});
} else {
obj.show();
}
});
};
$[pluginName] = function(elem, options) {
if (typeof elem === 'string') {
elem = $(elem);
}
return new Tooltip(elem, options);
};
$.fn[pluginName].defaults = {
contentElem: null
,contentAttr: null
,content: ''
,hoverable: true // should mouse over tooltip hold the tooltip or not?
,delay: 200
,cssclass: ''
,position: 'n' // n,s,e,w,ne,nw,se,sw,center
,autoclose: true
,appendTo: 'body' // where should the tooltips be appended to (default to document.body). Added for unit tests, not really needed in real life.
};
$(document).on('mouseenter.' + pluginName, '[data-trigger="tooltip"]', function() {
$(this)[pluginName]('do_mouseenter');
}).on('mouseleave.' + pluginName, '[data-trigger="tooltip"]', function() {
$(this)[pluginName]('do_mouseleave');
});
$(document).off('click.' + pluginName, '[data-dismiss="tooltip"]').on('click.' + pluginName, '[data-dismiss="tooltip"]', function(e) {
$(this).closest('.tooltip').trigger('close');
});
})(jQuery);