9086229a906e088fbf13ae102794aa643aafb00f
tdreszer
  Tue Aug 9 15:06:19 2011 -0700
Fixed some reentrant problems when image is dragScrolled and then ajax updated.  While much of this is >1x specific, not all is.  Also did some renaming to make functionality more clear.
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index bf25fe8..eb01104 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -945,60 +945,61 @@
 // Ends composite highlighting by mouse over
     var classList = $( this ).attr("class").split(" ");
     var btns = $( "p." + classList[0] );
     $( btns ).removeClass('btnBlue');
     $( btns ).addClass('btnGrey');
     if (jQuery.tableDnD != undefined) {
         var rows = imgTblContiguousRowSet($(this).parents('tr.trDraggable')[0]);
         if (rows)
            $( rows ).removeClass("trDrag");
     }
 }
 
 
 /////////////////////////////////////////////////////
 // Drag Scroll code
-jQuery.fn.panImages = function(imgOffset,imgBoxLeftOffset){
+jQuery.fn.panImages = function(){
     // globals across all panImages
-    var leftLimit   = imgBoxLeftOffset*-1;
+    originalPosition = getOriginalPosition();
+    var leftLimit   = hgTracks.imgBoxLeftLabel * -1;
     var rightLimit  = (hgTracks.imgBoxPortalWidth - hgTracks.imgBoxWidth + leftLimit);
-    var only1xScrolling = (hgTracks.imgBoxPortalOffsetX == 0);
-    var prevX       = (imgOffset + imgBoxLeftOffset)*-1;
+    var only1xScrolling = ((hgTracks.imgBoxWidth - hgTracks.imgBoxPortalWidth) == 0);//< hgTracks.imgBoxLeftLabel);
+    var prevX       = (hgTracks.imgBoxPortalOffsetX + hgTracks.imgBoxLeftLabel) * -1;
     var portalWidth = 0;
     var savedPosition;
-    panAdjustHeight(prevX);
 
     this.each(function(){
 
     var pic;
     var pan;
 
     if ( $(this).is("img") ) {
         pan = $(this).parent("div");
         pic = $(this);
     }
     else if ( $(this).is("div.scroller")  ) {
         pan = $(this);
         pic = $(this).children("img#panImg"); // Get the real pic
     }
 
     if(pan == undefined || pic == undefined) {
         throw "Not a div with a child image! 'panImages' can only be used with divs contain images.";
     }
 
     // globals across all panImages
     portalWidth     = $(pan).width();
+    panAdjustHeight(prevX);
     // globals to one panImage
     var newX        = 0;
     var mouseDownX  = 0;
     var mouseIsDown = false;
     var beyondImage = false;
     var atEdge      = false;
 
     initialize();
 
     function initialize(){
 
         if ( !($.browser.msie) ) // IE will override map items cursors as well!
             $(pan).parents('td.tdData').css('cursor',"url(../images/grabber.cur),w-resize");
 
         pan.mousedown(function(e){
@@ -1060,60 +1061,65 @@
                     panAdjustHeight(newX);  // NOTE: This will dynamically resize image while scrolling.  Do we want to?
             }
         }
     }
     function panMouseUp(e) {  // Must be a separate function instead of pan.mouseup event.
         //if(!e) e = window.event;
         if(mouseIsDown) {
 
             dragMaskClear();
             $(document).unbind('mousemove',panner);
             $(document).unbind('mouseup',panMouseUp);
             mouseIsDown = false;
             setTimeout('blockUseMap=false;',50); // Necessary incase the selectEnd was over a map item. select takes precedence.
 
             // Outside image?  Then abandon.
-            var curY = e.clientY;
+            var curY = e.pageY;
             var imgTbl = $('#imgTbl');
-            var imgTop = $(imgTbl).position().top;
-            if (curY < imgTop || curY > imgTop + $(imgTbl).height()) {
+            var north = $(imgTbl).offset().top;
+            var south = north + $(imgTbl).height();
+            if (curY < north || curY > south) {
                 atEdge = false;
                 beyondImage = false;
                 if (savedPosition != undefined)
                     setPosition(savedPosition,null);
                 var oldPos = prevX.toString() + "px";
                 $(".panImg").css( {'left': oldPos });
                 $('.tdData').css( {'backgroundPosition': oldPos } );
                 return true;
             }
 
             // Do we need to fetch anything?
             if(beyondImage) {
                 if(inPlaceUpdate) {
                     var pos = parsePosition(getPosition());
                     navigateInPlace("position=" + encodeURIComponent(pos.chrom + ":" + pos.start + "-" + pos.end));
                 } else {
                     document.TrackHeaderForm.submit();
                 }
                 return true; // Make sure the setTimeout below is not called.
             }
 
             // Just a normal scroll within a >1X image
             if(prevX != newX) {
-                //if (!only1xScrolling)
-                //    panAdjustHeight(newX); // NOTE: This will resize image after scrolling.  Do we want to while scrolling?
                 prevX = newX;
+                if (!only1xScrolling) {
+                    // panAdjustHeight(newX); // NOTE: This will resize image after scrolling.  Do we want to while scrolling?
+                    // This is important, since AJAX could lead to reinit after this within bounds scroll
+                    hgTracks.imgBoxPortalOffsetX = (prevX * -1) - hgTracks.imgBoxLeftLabel;
+                    hgTracks.imgBoxPortalLeft = newX.toString() + "px";
+                }
             }
         }
     }
     });  // end of this.each(function(){
 
     function panUpdatePosition(newOffsetX,bounded)
     {
         // Updates the 'position/search" display with change due to panning
         var portalWidthBases = hgTracks.imgBoxPortalEnd - hgTracks.imgBoxPortalStart;
         var portalScrolledX  = (hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel) + newOffsetX;
         var recalculate = false;
 
         var newPortalStart = hgTracks.imgBoxPortalStart - Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel); // As offset goes down, bases seen goes up!
         if( newPortalStart < hgTracks.chromStart && bounded) {     // Stay within bounds
             newPortalStart = hgTracks.chromStart;
@@ -1125,126 +1131,137 @@
             newPortalStart = newPortalEnd - portalWidthBases;
             recalculate = true;
         }
         if(newPortalStart > 0) {
             // XXXX ? hgTracks.imgBoxPortalStart = newPortalStart;
             // XXXX ? hgTracks.imgBoxPortalEnd = newPortalEnd;
             var newPos = hgTracks.chromName + ":" + commify(newPortalStart) + "-" + commify(newPortalEnd);
             setPosition(newPos, (newPortalEnd - newPortalStart + 1));
         }
         if (recalculate && hgTracks.imgBoxBasesPerPixel > 0) { // Need to recalculate X for bounding drag
             portalScrolledX = (hgTracks.imgBoxPortalStart - newPortalStart) / hgTracks.imgBoxBasesPerPixel;
             newOffsetX = portalScrolledX - (hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel);
         }
         return newOffsetX;
     }
-    function mapTopAndBottom(mapName,left,right)
+    function mapTopAndBottom(mapName,east,west)
     {
     // Find the top and bottom px given left and right boundaries
-        var span = { top: -10, bottom: -10 };
+        var mapPortal = { top: -10, bottom: -10 };
         var items = $("map[name='"+mapName+"']").children();
         if($(items).length>0) {
             $(items).each(function(t) {
                 var loc = this.coords.split(",");
                 var aleft   = parseInt(loc[0]);
                 var aright  = parseInt(loc[2]);
-                if(aleft < right && aright >= left) {
+                if(aleft < west && aright >= east) {
                     var atop    = parseInt(loc[1]);
                     var abottom = parseInt(loc[3]);
-                    if( span.top    < 0 ) {
-                        span.top    = atop;
-                        span.bottom = abottom;
-                    } else if(span.top > atop) {
-                            span.top = atop;
-                    } else if(span.bottom < abottom) {
-                            span.bottom = abottom;
+                    if( mapPortal.top    < 0 ) {
+                        mapPortal.top    = atop;
+                        mapPortal.bottom = abottom;
+                    } else if(mapPortal.top > atop) {
+                            mapPortal.top = atop;
+                    } else if(mapPortal.bottom < abottom) {
+                            mapPortal.bottom = abottom;
                     }
                 }
             });
         }
-        return span;
+        return mapPortal;
     }
     function panAdjustHeight(newOffsetX) {
         // Adjust the height of the track data images so that bed items scrolled off screen
         // do not waste vertical real estate
 
         // Is the > 1x?
         if (only1xScrolling)
             return;
 
-        var left = newOffsetX * -1;
-        var right = left + portalWidth;
+        var east = newOffsetX * -1;
+        var west = east + portalWidth;
         $(".panImg").each(function(t) {
-            var mapid  = "map_" + this.id.substring(4);
+            var mapid  = this.id.replace('img_','map_');
             var hDiv   = $(this).parent();
-            var top    = parseInt($(this).css("top")) * -1;
-            var bottom = top + $(hDiv).height();
+            var north  = parseInt($(this).css("top")) * -1;
+            var south  = north + $(hDiv).height();
 
-            var span = mapTopAndBottom(mapid,left,right);
-            if(span.top > 0) {
-                var topdif = Math.abs(span.top - top);
-                var botdif = Math.abs(span.bottom - bottom);
+            var mapPortal = mapTopAndBottom(mapid,east,west);
+            if(mapPortal.top > 0) {
+                var topdif = Math.abs(mapPortal.top - north);
+                var botdif = Math.abs(mapPortal.bottom - south);
                 if(topdif > 2 || botdif > 2) {
-                    $(hDiv).height( span.bottom - span.top );
-                    top = span.top * -1;
-                    $(this).css( {'top': top.toString() + "px" });
+                    $(hDiv).height( mapPortal.bottom - mapPortal.top );
+                    north = mapPortal.top * -1;
+                    $(this).css( {'top': north.toString() + "px" });
 
                     // Need to adjust side label height as well!
                     var imgId = this.id.split("_");
                     var titlePx = 0;
                     var center = $("#img_center_"+imgId[2]);
                     if(center.length > 0) {
                         titlePx = $(center).parent().height();
-                        top += titlePx;
+                        north += titlePx;
                     }
                     var side = $("#img_side_"+imgId[2]);
                     if( side.length > 0) {
-                        $(side).parent().height( span.bottom - span.top + titlePx);
-                        $(side).css( {'top': top.toString() + "px" });
+                        $(side).parent().height( mapPortal.bottom - mapPortal.top + titlePx);
+                        $(side).css( {'top': north.toString() + "px" });
                     }
                     var btn = $("#p_btn_"+imgId[2]);
                     if( btn.length > 0) {
-                        $(btn).height( span.bottom - span.top + titlePx);
+                        $(btn).height( mapPortal.bottom - mapPortal.top + titlePx);
                     } else {
                         btn = $("#img_btn_"+imgId[2]);
                         if( btn.length > 0) {
-                            $(btn).parent().height( span.bottom - span.top + titlePx);
+                            $(btn).parent().height( mapPortal.bottom - mapPortal.top + titlePx);
                             $(btn).css( {'top': top.toString() + "px" });
                         }
                     }
                 }
             }
         });
+        dragMaskResize();  // Resizes the dragMask to match current image size
     }
 
-    function dragMaskShow() {   // Sets up the waitMask to block page manipulation until cleared
+    function dragMaskShow() {   // Sets up the dragMask to show grabbing cursor within image and not allowed north and south of image
 
         var imgTbl = $('#imgTbl');
         // Find or create the waitMask (which masks the whole page)
         var  dragMask = $('div#dragMask');
         if( dragMask == undefined || dragMask.length == 0) {
             $("body").prepend("<div id='dragMask' class='waitMask'></div>");
             dragMask = $('div#dragMask');
         }
 
         $('body').css('cursor','not-allowed');
         $(dragMask).css('cursor',"url(../images/grabbing.cur),w-resize");
         $(dragMask).css({opacity:0.0,display:'block',top: $(imgTbl).position().top.toString() + 'px', height: $(imgTbl).height().toString() + 'px' });
         //$(dragMask).css({opacity:0.4,backgroundColor:'gray',zIndex:999}); // temporarily so I can see it
     }
 
-    function dragMaskClear() {        // Clears the waitMask
+    function dragMaskResize() {   // Resizes dragMask (called when image is dynamically resized in >1x scrolling)
+
+        var imgTbl = $('#imgTbl');
+        // Find or create the waitMask (which masks the whole page)
+        var  dragMask = $('div#dragMask');
+        if( dragMask != undefined && dragMask.length >= 1) {
+            $(dragMask).height( $(imgTbl).height() );
+        }
+    }
+
+    function dragMaskClear() {        // Clears the dragMask
         $('body').css('cursor','auto')
         var  dragMask = $('#dragMask');
         if( dragMask != undefined )
             $(dragMask).hide();
     }
 
 
 
 };
 
 /////////////////////////////////////////////////////
 
 function saveMouseOffset(ev)
 {   // Save the mouse offset associated with this event
     originalMouseOffset = {x: ev.clientX, y: ev.clientY};
@@ -1437,31 +1454,31 @@
                     if($(row).attr('rowIndex') != dragStartIndex) {
                         // NOTE Even if dragging a contiguous set of rows,
                         // still only need to check the one under the cursor.
                         if(imgTblSetOrder) {
                             imgTblSetOrder(table);
                         }
                         imgTblZipButtons( table );
                     }
                     $(document).unbind('mousemove',blockTheMapOnMouseMove);
                     setTimeout('blockUseMap=false;',50); // Necessary incase the onDrop was over a map item. onDrop takes precedence.
                 }
             });
         }
         if(hgTracks.imgBoxPortal) {
             // Turn on drag scrolling.
-            $("div.scroller").panImages(hgTracks.imgBoxPortalOffsetX,hgTracks.imgBoxLeftLabel);
+            $("div.scroller").panImages();
         }
         //$("#zoomSlider").slider({ min: -4, max: 3, step: 1 });//, handle: '.ui-slider-handle' });
 
         // Temporary warning while new imageV2 code is being worked through
         if($('#map').children("AREA").length > 0) {
             warn('Using imageV2, but old map is not empty!');
         }
 
         // Retrieve tracks via AJAX that may take too long to draw initialliy (i.e. a remote bigWig)
         var retrievables = $('#imgTbl').find("tr.mustRetrieve")
         if($(retrievables).length > 0) {
             $(retrievables).each( function (i) {
                 var trackName = $(this).attr('id').substring(3);
                 updateTrackImg(trackName,"","");
             });
@@ -2506,46 +2523,56 @@
 }
 
 function afterImgTblReload()
 {
 // Reload various UI widgets after updating imgTbl map.
     parseMap(null, true);
     $("map[name!=ideoMap]").each( function(t) { parseMap($(this, false));});
     initImgTblButtons();
     loadImgAreaSelect(false);
     // Do NOT reload context menu (otherwise we get the "context menu sticks" problem).
     // loadContextMenu($('#tr_' + id));
     if(trackImgTbl.tableDnDUpdate)
         trackImgTbl.tableDnDUpdate();
     reloadFloatingItem();
     // Turn on drag scrolling.
-    if(hgTracks.imgBoxPortal)
-        $("div.scroller").panImages(hgTracks.imgBoxPortalOffsetX,hgTracks.imgBoxLeftLabel);
+    if(hgTracks.imgBoxPortal) {
+        $("div.scroller").panImages();
+    }
 }
 
 function updateTrackImgForId(html, id)
 {
 // update row in imgTbl for given id.
 // return true if we successfully pull slice for id and update it in imgTrack.
     var str = "<TR id='tr_" + id + "'[^>]*>([\\s\\S]+?)</TR>";
     var reg = new RegExp(str);
     var a = reg.exec(html);
     if(a && a[1]) {
-        $('#tr_' + id).html(a[1]);
+        var tr = $('#tr_' + id);
+        $(tr).html(a[1]);
         // NOTE: Want to examine the png? Uncomment:
         //var img = $('#tr_' + id).find("img[id^='img_data_']").attr('src');
         //warn("Just parsed image:<BR>"+img);
+
+        // >1x dragScrolling needs some extra care.
+        if(hgTracks.imgBoxPortal && (hgTracks.imgBoxWidth > hgTracks.imgBoxPortalWidth)) {
+            if (hgTracks.imgBoxPortalLeft != undefined) {
+                $(tr).find('.panImg').css({'left': hgTracks.imgBoxPortalLeft });
+                $(tr).find('.tdData').css( {'backgroundPosition': hgTracks.imgBoxPortalLeft } );
+            }
+        }
         return true;
     } else {
         return false;
     }
 }
 
 function handleUpdateTrackMap(response, status)
 {
 // Handle ajax response with an updated trackMap image, map and optional ideogram.
 //
 // this.cmd can be used to figure out which menu item triggered this.
 // this.id == appropriate track if we are retrieving just a single track.
 
     // update local hgTracks.trackDb to reflect possible side-effects of ajax request.
     var json = scrapeVariable(response, "hgTracks");