[dw-free] Contextual hover menu doesn't work on jquerified pages
[commit: http://hg.dwscoalition.org/dw-free/rev/c29f3ee7ce3d]
http://bugs.dwscoalition.org/show_bug.cgi?id=2507
Make contextual hover work on jQuerified pages.
Patch by
fu.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=2507
Make contextual hover work on jQuerified pages.
Patch by
Files modified:
- cgi-bin/DW/BetaFeatures/journaljquery.pm
- cgi-bin/LJ/S2.pm
- cgi-bin/LJ/S2/EntryPage.pm
- cgi-bin/ljdefaults.pl
- cgi-bin/ljlib.pl
- cgi-bin/weblib.pl
- htdocs/js/contextualhover-jquery.js
- htdocs/js/jquery.ajaxtip.js
- htdocs/js/jquery.contextualhover.js
- htdocs/js/jquery.hoverIntent.js
- htdocs/js/jquery.hoverIntent.minified.js
- htdocs/stc/contextualhover.css
--------------------------------------------------------------------------------
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/DW/BetaFeatures/journaljquery.pm
--- a/cgi-bin/DW/BetaFeatures/journaljquery.pm Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/DW/BetaFeatures/journaljquery.pm Thu May 12 16:45:29 2011 +0800
@@ -26,10 +26,10 @@
"Thread expander",
"Same-page poll submission",
"Media embed placeholder expansion",
+ "Contextual hover",
);
my @notimplemented = (
- "Contextual hover",
"Icon browser",
"Same-page comment tracking",
);
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/LJ/S2.pm
--- a/cgi-bin/LJ/S2.pm Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/LJ/S2.pm Thu May 12 16:45:29 2011 +0800
@@ -191,13 +191,8 @@
# used if we're using our jquery library
LJ::need_res( { group => "jquery" }, qw(
js/md5.js
-
- js/jquery.ajaxtip.js
- js/tooltip.js
- js/tooltip.dynamic.js
- stc/ajaxtip.css
-
js/login-jquery.js
+
js/jquery.poll.js
js/jquery.mediaplaceholder.js
) );
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/LJ/S2/EntryPage.pm
--- a/cgi-bin/LJ/S2/EntryPage.pm Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/LJ/S2/EntryPage.pm Thu May 12 16:45:29 2011 +0800
@@ -382,7 +382,6 @@
LJ::need_res( "js/commentmanage.js" );
LJ::need_res( { group => "jquery" }, qw(
- js/jquery/jquery.ui.widget.js
js/jquery.ajaxtip.js
js/jquery.commentmanage.js
js/tooltip.js
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/ljdefaults.pl
--- a/cgi-bin/ljdefaults.pl Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/ljdefaults.pl Thu May 12 16:45:29 2011 +0800
@@ -354,6 +354,7 @@
"js/jquery/jquery.ui.sortable.js" => "js/jquery/jquery.ui.sortable.min.js",
"js/jquery/jquery.ui.widget.js" => "js/jquery/jquery.ui.widget.min.js",
+ "js/hoverIntent.js" => "js/hoverIntent.minified.js",
"js/tooltip.js" => "js/tooltip.min.js",
"js/tooltip.dynamic.js" => "js/tooltip.dynamic.min.js",
) unless defined %LJ::MINIFY;
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/ljlib.pl
--- a/cgi-bin/ljlib.pl Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/ljlib.pl Thu May 12 16:45:29 2011 +0800
@@ -1337,14 +1337,29 @@
if LJ::is_enabled('esn_ajax');
# contextual popup JS
- LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
- js/ippu.js
- js/lj_ippu.js
- js/hourglass.js
- js/contextualhover.js
- stc/contextualhover.css
- ))
- if $LJ::CTX_POPUP;
+ if ( $LJ::CTX_POPUP ) {
+ LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
+ js/ippu.js
+ js/lj_ippu.js
+ js/hourglass.js
+ js/contextualhover.js
+ stc/contextualhover.css
+ ));
+
+ LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY, group=> 'jquery' },
+ qw(
+ js/jquery/jquery.ui.widget.js
+
+ js/jquery.ajaxtip.js
+ js/tooltip.js
+ js/tooltip.dynamic.js
+ stc/ajaxtip.css
+
+ js/jquery.hoverIntent.js
+ js/jquery.contextualhover.js
+ stc/contextualhover.css
+ ));
+ }
# development JS
LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/weblib.pl
--- a/cgi-bin/weblib.pl Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/weblib.pl Thu May 12 16:45:29 2011 +0800
@@ -2567,9 +2567,16 @@
my $now = time();
my %list; # type -> [];
my %oldest; # type -> $oldest
+ my %included = ();
my $add = sub {
my ($type, $what, $modtime) = @_;
+ # the same file may have been included twice
+ # if it was in two different groups and not JS
+ # so add another check here
+ next if $included{$what};
+ $included{$what} = 1;
+
# in the concat-res case, we don't directly append the URL w/
# the modtime, but rather do one global max modtime at the
# end, which is done later in the tags function.
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/contextualhover-jquery.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/contextualhover-jquery.js Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,62 @@
+(function($) {
+ $.widget("ui.contextualhover", {
+ popupDelay: 500,
+ hideDelay: 250,
+ });
+
+ var ContextualHover = {
+ setup: function() {
+ if (!Site || !Site.ctx_popup) return;
+ if (Site.ctx_popup_userhead)
+ ContextualHover._initUserhead();
+
+ if (Site.ctx_popup_icons)
+ ContextualHover._initIcons();
+ },
+
+ _initUserhead: function() {
+ $("span.ljuser").each(function() {
+ var $usertag = $(this);
+ if ( $usertag.data("userdata") ) return;
+
+ $("img", $usertag).each(function() {
+ // if the parent (a tag with link to userinfo) has userid in its URL, then
+ // this is an openid user icon and we should use the userid
+ var $parent = $(this).parent("a[href]");
+ var data = {};
+ var userid;
+ if (userid = $parent.attr("href").match(/\?userid=(\d+)/i))
+ data.userid = userid[1];
+ else
+ data.username = $usertag.attr("lj:user");
+ if ( !data.username && !data.userid ) return;
+
+ $usertag.data("userdata", data).addClass("ContextualPopup");
+ });
+ });
+ },
+
+ _initIcons: function() {
+
+ $("img[src*='/userpic/']").each(function() {
+ if ( $(this).data("icon_url") ) return;
+ if (this.src.match(/userpic\..+\/\d+\/\d+/) ||
+ this.src.match(/\/userpic\/\d+\/\d+/)) {
+ $(this).data("icon_url", this.src).addClass("ContextualPopup");
+ }
+ });
+ }
+ }
+
+ // for init
+ $.extend({ contextualhover: ContextualHover.setup });
+
+})(jQuery);
+
+// initialize on page load
+$(function() {
+ $.contextualhover();
+ $(".ContextualPopup").live("mousemove", function(e){
+ console.log(e.target);
+ });
+});
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.ajaxtip.js
--- a/htdocs/js/jquery.ajaxtip.js Thu May 12 16:42:01 2011 +0800
+++ b/htdocs/js/jquery.ajaxtip.js Thu May 12 16:45:29 2011 +0800
@@ -1,7 +1,8 @@
(function($) {
$.widget("dw.ajaxtip", {
options: {
- content: undefined
+ content: undefined,
+ tooltip: { dynamic: true }
},
_namespace: function() {
return this.options.namespace ? "."+this.options.namespace : "";
@@ -10,31 +11,31 @@
var self = this;
var ns = self._namespace();
- var tipcontainer = $("<div class='ajaxtooltip' style='display: none'></div>")
+ var tipcontainer = $("<div class='ajaxtooltip ajaxtip' style='display: none'></div>")
.click(function(e) {e.stopPropagation()})
self.element
.after(tipcontainer)
.bind("ajaxresult"+ns, function(e) {
- self.element.data("tooltip").getTip()
- .addClass("ajaxresult-" + e.ajaxresult.status)
- .text(e.ajaxresult.message)
+ var tip = self.element.data("tooltip").getTip()
+ .addClass("ajaxresult-" + e.ajaxresult.status);
+ if ( e.ajaxresult.message ) tip.text(e.ajaxresult.message);
})
- .tooltip({
+ .tooltip($.extend({
predelay: 0,
delay: 1500,
events: {
- def: "ajaxstart"+ns+",ajaxresult"+ns,
+ def: "ajaxstart"+ns+", tooltipout"+ns+" ajaxresult"+ns,
widget: "ajaxstart"+ns+",ajaxresult"+ns,
tooltip: "mouseover,mouseleave"
},
position: "bottom center",
relative: true,
effect: "fade",
-
onBeforeShow: function(e) {
var tip = this.getTip();
- tip.removeClass("ajaxresult ajaxresult-success ajaxresult-error");
+ tip.removeClass("ajaxresult ajaxresult-success ajaxresult-error")
+ .appendTo("body");
if ( self.options.content && ! this.inprogress ){
tip.html(self.options.content)
@@ -43,8 +44,10 @@
.addClass("ajaxresult")
}
}
- })
- .dynamic({ classNames: "tip-top tip-right tip-bottom tip-left" });
+ }, self.options.tooltip));
+ if ( self.options.tooltip.dynamic )
+ self.element.dynamic({ classNames: "tip-top tip-right tip-bottom tip-left" });
+
},
_init: function() {
if(this.options.content)
@@ -64,6 +67,11 @@
var tip = this.element.data("tooltip");
if( tip && tip.isShown() ) tip.hide();
},
+ show: function() {
+ var tip = this.element.data("tooltip");
+ tip.show();
+ this.success(/*no msg*/);
+ },
load: function(opts) {
var self = this;
@@ -77,7 +85,7 @@
self.element.trigger("ajaxstart" + self._namespace());
$.ajax({
- type: "POST",
+ type: opts.formmethod || "POST",
url : opts.endpoint ? self._endpointurl( opts.endpoint) : opts.url,
data: opts.data,
@@ -107,7 +115,7 @@
$.extend( $.dw.ajaxtip, {
closeall: function() {
- $(".ajaxtooltip:visible").each(
+ $(".ajaxtip:visible").each(
function(){
var tip = $(this).prev().data("tooltip");
if ( !tip.inprogress ) tip.hide()
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.contextualhover.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.contextualhover.js Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,376 @@
+(function($, Site){
+
+$.fn.contextualhoversetup = function() {
+ if (!Site || !Site.ctx_popup) return;
+
+ this.each(function() {
+ if (Site.ctx_popup_userhead)
+ _initUserhead(this);
+
+ if (Site.ctx_popup_icons)
+ _initIcons(this);
+ });
+};
+
+function _initUserhead(context) {
+ $("span.ljuser",context).each(function() {
+ var $usertag = $(this);
+
+ $("img", $usertag).each(function() {
+ // if the parent (a tag with link to userinfo) has userid in its URL, then
+ // this is an openid user icon and we should use the userid
+ var $head = $(this);
+ var $parent = $head.parent("a[href]");
+ var data = {};
+ var userid;
+ if (userid = $parent.attr("href").match(/\?userid=(\d+)/i))
+ data.userid = userid[1];
+ else
+ data.username = $usertag.attr("lj:user");
+ if ( !data.username && !data.userid ) return;
+
+ data.type = "user";
+ $head.contextualhover( data );
+ });
+ });
+}
+
+function _initIcons(context) {
+ $("img[src*='/userpic/']",context).each(function() {
+ var $icon = $(this);
+ if (this.src.match(/userpic\..+\/\d+\/\d+/) ||
+ this.src.match(/\/userpic\/\d+\/\d+/)) {
+ $icon.contextualhover({ "icon_url": this.src, type: "icon" });
+ }
+ });
+}
+
+})(jQuery, Site);
+
+$.widget("dw.contextualhover", {
+options: {
+ disableAJAX: false,
+
+ username: undefined,
+ userid: undefined,
+ icon_url: undefined,
+ type: undefined // icon or user(head)
+},
+_create: function() {
+ var self = this;
+ var opts = self.options;
+
+ if ( opts.type == "icon" ) {
+ self.element.removeAttr("title");
+ var parent = self.element.parent("a")
+ if ( parent.length > 0 )
+ self.element = parent;
+ }
+
+ var trigger = self.element;
+ trigger.addClass("ContextualPopup");
+
+ var action = $.fn.hoverIntent ? "hoverIntent" : "hover";
+ trigger[action](
+ function() {
+ if ( self.cachedResults ) {
+ self._renderPopup();
+ self._trigger("complete");
+ return;
+ }
+
+ trigger.ajaxtip({ namespace: "contextualpopup", tooltip: {position: "bottom left",dynamic:false} })
+ .ajaxtip( "load", {
+ endpoint: "ctxpopup",
+ formmethod: "GET",
+ data: {
+ "user": opts.username || "",
+ "userid": opts.userid || 0,
+ "userpic_url": opts.icon_url || "",
+ "mode": "getinfo"
+ },
+ success: function( data, status, jqxhr ) {
+ if ( data.error ) {
+ if ( data.noshow )
+ trigger.ajaxtip( "cancel" );
+ else
+ trigger.ajaxtip( "error", data.error )
+ } else {
+ self.cachedResults = data;
+ self._renderPopup();
+
+ // expire cache after 5 minutes
+ setTimeout(function() {
+ self.cachedResults = null;
+ }, 60 * 5 * 1000);
+ }
+ self._trigger("complete");
+ },
+ error: function( jqxhr, status, error ) {
+ trigger.ajaxtip( "error", "Error contacting server. " + error);
+ self._trigger( "complete" );
+ }
+ });
+ },
+ function() {
+ }
+ )
+},
+
+_renderPopup: function() {
+ var self = this;
+ var opts = self.options;
+ var data = self.cachedResults;
+
+ if ( data && ( !data.username || !data.success || data.noshow ) ) {
+ this.element.ajaxtip("cancel");
+ }
+
+ this.element.ajaxtip("show")
+
+ var $inner = $("<div class='Inner'></div>");
+ var $content = $("<div class='Content'></div>");
+
+ if ( data.url_userpic ) {
+ var $link = $("<a></a>", { href: data.url_allpics });
+ var $icon = $("<img>", { src: data.url_userpic }).attr({width: data.userpic_w, height: data.userpic_h});
+ var $container = $("<div class='Userpic'></div>").append($link.append($icon));
+
+ $inner.append($container);
+ }
+
+ $inner.append($content);
+
+ var username = data.display_username;
+ var $relation = $("<div class='Relation'></div>");
+ var strings = {
+ member: "You are a member of " + username,
+ watching: "You have subscribed to " + username,
+ watched_by: username + " has subscribed to you",
+ mutual_watch: username + " and you have mutual subscriptions",
+ trusting: "You have granted access to " + username,
+ trusted_by: username + " has granted access to you",
+ mutual_trust: username + " and you have mutual access",
+ self: "This is you"
+ };
+ if ( data.is_comm ) {
+ var rels = [];
+ if (data.is_member) rels.push(strings.member);
+ if (data.is_watching) rels.push(strings.watching);
+
+ $relation.html(rels.length > 0 ? rels.join("<br />") : username);
+ } else if (data.is_syndicated ) {
+ $relation.html(data.is_watching ? strings.watching : username);
+ } else if (data.is_requester) {
+ $relation.html( strings.self );
+ } else {
+ var rels = [];
+ if ( data.is_trusting && data.is_trusted_by )
+ rels.push(strings.mutual_trust);
+ else if ( data.is_trusting )
+ rels.push(strings.trusting);
+ else if ( data.is_trusted_by )
+ rels.push(strings.trusted_by);
+
+ if ( data.is_watching && data.is_watched_by )
+ rels.push(strings.mutual_watch);
+ else if ( data.is_watching )
+ rels.push(strings.watching);
+ else if ( data.is_watched_by )
+ rels.push(strings.watched_by);
+
+ $relation.html(rels.length > 0 ? rels.join("<br />") : username);
+ }
+ $content.append($relation);
+
+ if ( data.is_logged_in && data.is_comm ) {
+ var $membership = $("<span></span>");
+ if ( ! data.is_closed_membership || data.is_member ) {
+ var $membershiplink = $("<a></a>");
+ var $membershipaction = data.is_member ? "leave" : "join";
+
+ if ( data.is_member )
+ $membershiplink.attr("href" , data.url_leavecomm ).html("Leave");
+ else
+ $membershiplink.attr("href", data.url_joincomm ).html("Join community");
+
+ if ( ! opts.disableAJAX ) {
+ $membershiplink.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, membership_action, this, e);
+ });
+ }
+
+ $membership.append($membershiplink);
+ } else {
+ $membership.html("Community closed");
+ }
+ $content.append($membership);
+ }
+
+ var links = [];
+ if ( data.is_logged_in && ( data.is_person || data.is_identity ) && data.can_message ) {
+ var $sendmessage = $("<a></a>", { href: data.url_message }).html("Send message");
+ links.push($("<span></span>").append($sendmessage));
+ }
+
+ // relationships
+ if ( data.is_logged_in && ! data.is_requester ) {
+ if ( ! data.is_trusting ) {
+ if ( data.is_person || data.other_is_identity ) {
+ var $addtrust = $("<a></a>", { href: data.url_addtrust } ).html("Grant access");
+ links.push($("<span class='AddTrust'></span>").append($addtrust));
+
+ if( ! opts.disableAJAX ) {
+ $addtrust.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "addTrust", this, e);
+ });
+ }
+ }
+ } else {
+ if ( data.is_person || data.other_is_identity ) {
+ var $removetrust = $("<a></a>", { href: data.url_addtrust } ).html("Remove access");
+ links.push($("<span class='RemoveTrust'></span>").append($removetrust));
+
+ if( ! opts.disableAJAX ) {
+ $removetrust.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "removeTrust", this, e);
+ });
+ }
+ }
+ }
+
+ if ( !data.is_watching && !data.other_is_identity ) {
+ var $addwatch = $("<a></a>", { href: data.url_addwatch } ).html("Subscribe");
+ links.push($("<span class='AddWatch'></span>").append($addwatch));
+
+ if( ! opts.disableAJAX ) {
+ $addwatch.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "addWatch", this, e);
+ });
+ }
+ } else if ( data.is_watching ) {
+ var $removewatch = $("<a></a>", { href: data.url_addwatch } ).html("Remove subscription");
+ links.push($("<span class='RemoveWatch'></span>").append($removewatch));
+
+ if( ! opts.disableAJAX ) {
+ $removewatch.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "removeWatch", this, e);
+ });
+ }
+ }
+ $relation.addClass("RelationshipStatus");
+ }
+
+ // FIXME: double-check this when vgifts come out
+ if ( ( data.is_person || data.is_comm ) && ! data.is_requester && data.can_receive_vgifts ) {
+ var $sendvgift = $("<a></a>", { href: Site.siteroot + "/shop/vgift?to=" + data.username })
+ .html("Send a virtual gift");
+ links.push($("<span></span").append($sendvgift));
+ }
+
+ if ( data.is_logged_in && ! data.is_requester && ! data.is_syndicated ) {
+ if ( data.is_banned ) {
+ var $unbanlink = $("<a></a>", { href: Site.siteroot + "/manage/banusers" });
+ $unbanlink.html( data.is_comm ? "Unban community" : "Unban user" );
+ if( ! opts.disableAJAX ) {
+ $unbanlink.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "setUnban", this, e);
+ });
+ }
+ links.push($("<span class='SetUnban'></span>").append($unbanlink));
+
+ } else {
+ var $banlink = $("<a></a>", { href: Site.siteroot + "/manage/banusers" });
+ $banlink.html( data.is_comm ? "Ban community" : "Ban user" );
+ if( ! opts.disableAJAX ) {
+ $banlink.click(function(e) {
+ e.stopPropagation(); e.preventDefault();
+ self._changeRelation(data, "setBan", this, e);
+ });
+ }
+ links.push($("<span class='SetBan'></span>").append($banlink));
+ }
+ }
+
+ var linkslength = links.length;
+ $.each(links,function(index) {
+ $content.append(this);
+ $content.append("<br>");
+ });
+
+ $("<span>View: </span>").appendTo($content);
+ if ( data.is_person || data.is_comm || data.is_syndicated ) {
+ var $journallink = $("<a></a>", {href: data.url_journal});
+ if (data.is_person)
+ $journallink.html("Journal");
+ else if ( data.is_comm )
+ $journallink.html("Community");
+ else if ( data.is_syndicated )
+ $journallink.html("Feed");
+
+ $content.append(
+ $journallink,
+ $("<span> | </span>"),
+ $("<a></a>", { href: data.url_profile} ).html("Profile")
+ );
+ }
+
+ $content.append($("<div class='ljclear'> </div>"));
+
+ this.element
+ .ajaxtip("widget")
+ .removeClass("ajaxresult ajaxtooltip").addClass("ContextualPopup")
+ .empty().append($inner)
+},
+
+_changeRelation: function(info, action, link, e) {
+ if ( !info ) return;
+ var self = this;
+ var $link = $(link);
+
+ $link.ajaxtip({namespace: "changerelation"}).ajaxtip("load", {
+ endpoint: "changerelation",
+ data: {
+ target: info.username,
+ action: action,
+ auth_token: info[action+"_authtoken"]
+ },
+ success: function( data, status, jqxhr ) {
+ if ( data.error ) {
+ $link.ajaxtip( "error", data.error )
+ } else if ( ! data.success ) {
+ $link.ajaxtip( "error", "Did not change relation successfully" );
+ self._renderPopup(data);
+ } else {
+ if ( self.cachedResults ) {
+ var updatedProps = [ "is_trusting", "is_watching", "is_member", "is_banned" ];
+ $.each(updatedProps,function(){
+ self.cachedResults[this]=data[this];
+ });
+ }
+ self._renderPopup();
+ }
+ self._trigger("complete");
+ },
+ error: function( jqxhr, status, error ) {
+ $link.ajaxtip( "error", "Error contacting server. " + error);
+ self._trigger( "complete" );
+ }
+ });
+}
+
+});
+
+jQuery(document).ready(function($){
+ $(document).contextualhoversetup();
+ $(document.body).delegate( "*", "updatedcontent.entry.poll.comment", function(e) {
+ e.stopPropagation();
+ $(this).contextualhoversetup();
+ });
+});
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.hoverIntent.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.hoverIntent.js Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,106 @@
+/**
+* hoverIntent is similar to jQuery's built-in "hover" function except that
+* instead of firing the onMouseOver event immediately, hoverIntent checks
+* to see if the user's mouse has slowed down (beneath the sensitivity
+* threshold) before firing the onMouseOver event.
+*
+* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* hoverIntent is currently available for use in all personal or commercial
+* projects under both MIT and GPL licenses. This means that you can choose
+* the license that best suits your project, and use it accordingly.
+*
+* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
+* $("ul li").hoverIntent( showNav , hideNav );
+*
+* // advanced usage receives configuration object only
+* $("ul li").hoverIntent({
+* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
+* interval: 100, // number = milliseconds of polling interval
+* over: showNav, // function = onMouseOver callback (required)
+* timeout: 0, // number = milliseconds delay before onMouseOut function call
+* out: hideNav // function = onMouseOut callback (required)
+* });
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne brian(at)cherne(dot)net
+*/
+(function($) {
+ $.fn.hoverIntent = function(f,g) {
+ // default configuration options
+ var cfg = {
+ sensitivity: 7,
+ interval: 100,
+ timeout: 0
+ };
+ // override configuration options with user supplied object
+ cfg = $.extend(cfg, g ? { over: f, out: g } : f );
+
+ // instantiate variables
+ // cX, cY = current X and Y position of mouse, updated by mousemove event
+ // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
+ var cX, cY, pX, pY;
+
+ // A private function for getting mouse position
+ var track = function(ev) {
+ cX = ev.pageX;
+ cY = ev.pageY;
+ };
+
+ // A private function for comparing current and previous mouse position
+ var compare = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ // compare mouse positions to see if they've crossed the threshold
+ if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
+ $(ob).unbind("mousemove",track);
+ // set hoverIntent state to true (so mouseOut can be called)
+ ob.hoverIntent_s = 1;
+ return cfg.over.apply(ob,[ev]);
+ } else {
+ // set previous coordinates for next time
+ pX = cX; pY = cY;
+ // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
+ ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
+ }
+ };
+
+ // A private function for delaying the mouseOut function
+ var delay = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ ob.hoverIntent_s = 0;
+ return cfg.out.apply(ob,[ev]);
+ };
+
+ // A private function for handling mouse 'hovering'
+ var handleHover = function(e) {
+ // copy objects to be passed into t (required for event object to be passed in IE)
+ var ev = jQuery.extend({},e);
+ var ob = this;
+
+ // cancel hoverIntent timer if it exists
+ if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
+
+ // if e.type == "mouseenter"
+ if (e.type == "mouseenter") {
+ // set "previous" X and Y position based on initial entry point
+ pX = ev.pageX; pY = ev.pageY;
+ // update "current" X and Y position based on mousemove
+ $(ob).bind("mousemove",track);
+ // start polling interval (self-calling timeout) to compare mouse coordinates over time
+ if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
+
+ // else e.type == "mouseleave"
+ } else {
+ // unbind expensive mousemove event
+ $(ob).unbind("mousemove",track);
+ // if hoverIntent state is true, then call the mouseOut function after the specified delay
+ if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
+ }
+ };
+
+ // bind the function to the two event listeners
+ return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
+ };
+})(jQuery);
\ No newline at end of file
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.hoverIntent.minified.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.hoverIntent.minified.js Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,9 @@
+/**
+* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne brian(at)cherne(dot)net
+*/
+(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type=="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover)}})(jQuery);
\ No newline at end of file
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/stc/contextualhover.css
--- a/htdocs/stc/contextualhover.css Thu May 12 16:42:01 2011 +0800
+++ b/htdocs/stc/contextualhover.css Thu May 12 16:45:29 2011 +0800
@@ -3,6 +3,7 @@
margin: 5px 0 0 20px;
font: normal 11px "Arial", "Verdana", sans-serif !important;
text-align: left;
+ z-index: 99;
}
* html div.ContextualPopup {
--------------------------------------------------------------------------------
