365 lines
8.0 KiB
JavaScript
365 lines
8.0 KiB
JavaScript
|
/**
|
||
|
* CHANGES MADE BY YII DEVELOPERS, READ CAREFULLY BEFORE UPGRADING THIS FILE:
|
||
|
* 1. This commit has been used:
|
||
|
* https://github.com/jquerytools/jquerytools/commit/4f3f3f14e83b0ff276a795e9f45400930904adff#src/tooltip/tooltip.js
|
||
|
* 2. Original `$.fn.tooltip` has been changed to `$.fn.tooltip2` to prevent conflict between jQuery UI Tooltip and
|
||
|
* jQuery Tools Tooltip.
|
||
|
*
|
||
|
* @license
|
||
|
* jQuery Tools @VERSION Tooltip - UI essentials
|
||
|
*
|
||
|
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
|
||
|
*
|
||
|
* http://flowplayer.org/tools/tooltip/
|
||
|
*
|
||
|
* Since: November 2008
|
||
|
* Date: @DATE
|
||
|
*/
|
||
|
(function($) {
|
||
|
// static constructs
|
||
|
$.tools = $.tools || {version: '@VERSION'};
|
||
|
|
||
|
$.tools.tooltip = {
|
||
|
|
||
|
conf: {
|
||
|
|
||
|
// default effect variables
|
||
|
effect: 'toggle',
|
||
|
fadeOutSpeed: "fast",
|
||
|
predelay: 0,
|
||
|
delay: 30,
|
||
|
opacity: 1,
|
||
|
tip: 0,
|
||
|
fadeIE: false, // enables fade effect in IE
|
||
|
|
||
|
// 'top', 'bottom', 'right', 'left', 'center'
|
||
|
position: ['top', 'center'],
|
||
|
offset: [0, 0],
|
||
|
relative: false,
|
||
|
cancelDefault: true,
|
||
|
|
||
|
// type to event mapping
|
||
|
events: {
|
||
|
def: "mouseenter,mouseleave",
|
||
|
input: "focus,blur",
|
||
|
widget: "focus mouseenter,blur mouseleave",
|
||
|
tooltip: "mouseenter,mouseleave"
|
||
|
},
|
||
|
|
||
|
// 1.2
|
||
|
layout: '<div/>',
|
||
|
tipClass: 'tooltip'
|
||
|
},
|
||
|
|
||
|
addEffect: function(name, loadFn, hideFn) {
|
||
|
effects[name] = [loadFn, hideFn];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
var effects = {
|
||
|
toggle: [
|
||
|
function(done) {
|
||
|
var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
|
||
|
if (o < 1) { tip.css({opacity: o}); }
|
||
|
tip.show();
|
||
|
done.call();
|
||
|
},
|
||
|
|
||
|
function(done) {
|
||
|
this.getTip().hide();
|
||
|
done.call();
|
||
|
}
|
||
|
],
|
||
|
|
||
|
fade: [
|
||
|
function(done) {
|
||
|
var conf = this.getConf();
|
||
|
if (!$.browser.msie || conf.fadeIE) {
|
||
|
this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
|
||
|
}
|
||
|
else {
|
||
|
this.getTip().show();
|
||
|
done();
|
||
|
}
|
||
|
},
|
||
|
function(done) {
|
||
|
var conf = this.getConf();
|
||
|
if (!$.browser.msie || conf.fadeIE) {
|
||
|
this.getTip().fadeOut(conf.fadeOutSpeed, done);
|
||
|
}
|
||
|
else {
|
||
|
this.getTip().hide();
|
||
|
done();
|
||
|
}
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
|
||
|
/* calculate tip position relative to the trigger */
|
||
|
function getPosition(trigger, tip, conf) {
|
||
|
|
||
|
|
||
|
// get origin top/left position
|
||
|
var top = conf.relative ? trigger.position().top : trigger.offset().top,
|
||
|
left = conf.relative ? trigger.position().left : trigger.offset().left,
|
||
|
pos = conf.position[0];
|
||
|
|
||
|
top -= tip.outerHeight() - conf.offset[0];
|
||
|
left += trigger.outerWidth() + conf.offset[1];
|
||
|
|
||
|
// iPad position fix
|
||
|
if (/iPad/i.test(navigator.userAgent)) {
|
||
|
top -= $(window).scrollTop();
|
||
|
}
|
||
|
|
||
|
// adjust Y
|
||
|
var height = tip.outerHeight() + trigger.outerHeight();
|
||
|
if (pos == 'center') { top += height / 2; }
|
||
|
if (pos == 'bottom') { top += height; }
|
||
|
|
||
|
|
||
|
// adjust X
|
||
|
pos = conf.position[1];
|
||
|
var width = tip.outerWidth() + trigger.outerWidth();
|
||
|
if (pos == 'center') { left -= width / 2; }
|
||
|
if (pos == 'left') { left -= width; }
|
||
|
|
||
|
return {top: top, left: left};
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
function Tooltip(trigger, conf) {
|
||
|
|
||
|
var self = this,
|
||
|
fire = trigger.add(self),
|
||
|
tip,
|
||
|
timer = 0,
|
||
|
pretimer = 0,
|
||
|
title = trigger.attr("title"),
|
||
|
tipAttr = trigger.attr("data-tooltip"),
|
||
|
effect = effects[conf.effect],
|
||
|
shown,
|
||
|
|
||
|
// get show/hide configuration
|
||
|
isInput = trigger.is(":input"),
|
||
|
isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
|
||
|
type = trigger.attr("type"),
|
||
|
evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];
|
||
|
|
||
|
|
||
|
// check that configuration is sane
|
||
|
if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }
|
||
|
|
||
|
evt = evt.split(/,\s*/);
|
||
|
if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }
|
||
|
|
||
|
|
||
|
// trigger --> show
|
||
|
trigger.on(evt[0], function(e) {
|
||
|
|
||
|
clearTimeout(timer);
|
||
|
if (conf.predelay) {
|
||
|
pretimer = setTimeout(function() { self.show(e); }, conf.predelay);
|
||
|
|
||
|
} else {
|
||
|
self.show(e);
|
||
|
}
|
||
|
|
||
|
// trigger --> hide
|
||
|
}).on(evt[1], function(e) {
|
||
|
clearTimeout(pretimer);
|
||
|
if (conf.delay) {
|
||
|
timer = setTimeout(function() { self.hide(e); }, conf.delay);
|
||
|
|
||
|
} else {
|
||
|
self.hide(e);
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
|
||
|
// remove default title
|
||
|
if (title && conf.cancelDefault) {
|
||
|
trigger.removeAttr("title");
|
||
|
trigger.data("title", title);
|
||
|
}
|
||
|
|
||
|
$.extend(self, {
|
||
|
|
||
|
show: function(e) {
|
||
|
|
||
|
// tip not initialized yet
|
||
|
if (!tip) {
|
||
|
|
||
|
// data-tooltip
|
||
|
if (tipAttr) {
|
||
|
tip = $(tipAttr);
|
||
|
|
||
|
// single tip element for all
|
||
|
} else if (conf.tip) {
|
||
|
tip = $(conf.tip).eq(0);
|
||
|
|
||
|
// autogenerated tooltip
|
||
|
} else if (title) {
|
||
|
tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
|
||
|
.hide().append(title);
|
||
|
|
||
|
// manual tooltip
|
||
|
} else {
|
||
|
tip = trigger.next();
|
||
|
if (!tip.length) { tip = trigger.parent().next(); }
|
||
|
}
|
||
|
|
||
|
if (!tip.length) { throw "Cannot find tooltip for " + trigger; }
|
||
|
}
|
||
|
|
||
|
if (self.isShown()) { return self; }
|
||
|
|
||
|
// stop previous animation
|
||
|
tip.stop(true, true);
|
||
|
|
||
|
// get position
|
||
|
var pos = getPosition(trigger, tip, conf);
|
||
|
|
||
|
// restore title for single tooltip element
|
||
|
if (conf.tip) {
|
||
|
tip.html(trigger.data("title"));
|
||
|
}
|
||
|
|
||
|
// onBeforeShow
|
||
|
e = $.Event();
|
||
|
e.type = "onBeforeShow";
|
||
|
fire.trigger(e, [pos]);
|
||
|
if (e.isDefaultPrevented()) { return self; }
|
||
|
|
||
|
|
||
|
// onBeforeShow may have altered the configuration
|
||
|
pos = getPosition(trigger, tip, conf);
|
||
|
|
||
|
// set position
|
||
|
tip.css({position:'absolute', top: pos.top, left: pos.left});
|
||
|
|
||
|
shown = true;
|
||
|
|
||
|
// invoke effect
|
||
|
effect[0].call(self, function() {
|
||
|
e.type = "onShow";
|
||
|
shown = 'full';
|
||
|
fire.trigger(e);
|
||
|
});
|
||
|
|
||
|
|
||
|
// tooltip events
|
||
|
var event = conf.events.tooltip.split(/,\s*/);
|
||
|
|
||
|
if (!tip.data("__set")) {
|
||
|
|
||
|
tip.off(event[0]).on(event[0], function() {
|
||
|
clearTimeout(timer);
|
||
|
clearTimeout(pretimer);
|
||
|
});
|
||
|
|
||
|
if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
|
||
|
tip.off(event[1]).on(event[1], function(e) {
|
||
|
|
||
|
// being moved to the trigger element
|
||
|
if (e.relatedTarget != trigger[0]) {
|
||
|
trigger.trigger(evt[1].split(" ")[0]);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// bind agein for if same tip element
|
||
|
if (!conf.tip) tip.data("__set", true);
|
||
|
}
|
||
|
|
||
|
return self;
|
||
|
},
|
||
|
|
||
|
hide: function(e) {
|
||
|
|
||
|
if (!tip || !self.isShown()) { return self; }
|
||
|
|
||
|
// onBeforeHide
|
||
|
e = $.Event();
|
||
|
e.type = "onBeforeHide";
|
||
|
fire.trigger(e);
|
||
|
if (e.isDefaultPrevented()) { return; }
|
||
|
|
||
|
shown = false;
|
||
|
|
||
|
effects[conf.effect][1].call(self, function() {
|
||
|
e.type = "onHide";
|
||
|
fire.trigger(e);
|
||
|
});
|
||
|
|
||
|
return self;
|
||
|
},
|
||
|
|
||
|
isShown: function(fully) {
|
||
|
return fully ? shown == 'full' : shown;
|
||
|
},
|
||
|
|
||
|
getConf: function() {
|
||
|
return conf;
|
||
|
},
|
||
|
|
||
|
getTip: function() {
|
||
|
return tip;
|
||
|
},
|
||
|
|
||
|
getTrigger: function() {
|
||
|
return trigger;
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
// callbacks
|
||
|
$.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
|
||
|
|
||
|
// configuration
|
||
|
if ($.isFunction(conf[name])) {
|
||
|
$(self).on(name, conf[name]);
|
||
|
}
|
||
|
|
||
|
// API
|
||
|
self[name] = function(fn) {
|
||
|
if (fn) { $(self).on(name, fn); }
|
||
|
return self;
|
||
|
};
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// jQuery plugin implementation
|
||
|
$.fn.tooltip2 = function(conf) {
|
||
|
|
||
|
// return existing instance
|
||
|
var api = this.data("tooltip");
|
||
|
if (api) { return api; }
|
||
|
|
||
|
conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
|
||
|
|
||
|
// position can also be given as string
|
||
|
if (typeof conf.position == 'string') {
|
||
|
conf.position = conf.position.split(/,?\s/);
|
||
|
}
|
||
|
|
||
|
// install tooltip for each entry in jQuery object
|
||
|
this.each(function() {
|
||
|
api = new Tooltip($(this), conf);
|
||
|
$(this).data("tooltip", api);
|
||
|
});
|
||
|
|
||
|
return conf.api ? api: this;
|
||
|
};
|
||
|
|
||
|
}) (jQuery);
|
||
|
|
||
|
|
||
|
|