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