/* ==========================================================
* 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);