20272ff09554a919bc93d6b3c14c90702fda84fc
larrym
  Wed Sep 21 21:27:48 2011 -0700
keep what the user last was looking at in view after in-place updates; fix stuff discovered via use strict (e.g. undeclared variables)
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 8a058b2..ab78826 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -6,35 +6,37 @@
 var originalCursor;
 var originalMouseOffset = {x:0, y:0};
 var startDragZoom = null;
 var imageV2 = false;
 var blockUseMap = false;
 var mapItems;
 var trackImg;               // jQuery element for the track image
 var trackImgTbl;            // jQuery element used for image table under imageV2
 var imgAreaSelect;          // jQuery element used for imgAreaSelect
 var originalImgTitle;
 var autoHideSetting = true; // Current state of imgAreaSelect autoHide setting
 var selectedMenuItem;       // currently choosen context menu item (via context menu).
 var browser;                // browser ("msie", "safari" etc.)
 var mapIsUpdateable = true;
 var currentMapItem;
+var lastMapItem;            // this is used to try to keep what the user last was looking at in view after in-place updates.
 var floatingMenuItem;
 var visibilityStrsOrder = new Array("hide", "dense", "full", "pack", "squish");     // map browser numeric visibility codes to strings
 var supportZoomCodon = false;  // turn on experimental zoom-to-codon functionality (currently only on in larrym's tree).
 var inPlaceUpdate = false;     // modified based on value of hgTracks.inPlaceUpdate and mapIsUpdateable
 var contextMenu;
+var lastMatchLast;
 
 /* Data passed in from CGI via the hgTracks object:
  *
  * string cgiVersion          // CGI_VERSION
  * string chromName           // current chromosome
  * int winStart               // genomic start coordinate (0-based, half-open)
  * int winEnd                 // genomic end coordinate
  * int newWinWidth            // new width (in bps) if user clicks on the top ruler
  * boolean dragSelection      // true if we should allow drag and select
  * boolean revCmplDisp        // true if we are in reverse display
  * int insideX                // width of side-bar (in pixels)
  * int rulerClickHeight       // height of ruler (in pixels)
  * boolean dragSelection      // true if drag-and-select turned on
  * boolean inPlaceUpdate      // true if in-place-update is turned on
  * int imgBox*                // various drag-scroll values
@@ -313,34 +315,34 @@
 function selectEnd(img, selection)
 {
     var now = new Date();
     var doIt = false;
     if(originalCursor != null)
         jQuery('body').css('cursor', originalCursor);
     // ignore releases outside of the image rectangle (allowing a 10 pixel slop)
     if(autoHideSetting && checkPosition(img, selection)) {
        // ignore single clicks that aren't in the top of the image (this happens b/c the clickClipHeight test in selectStart
        // doesn't occur when the user single clicks).
        doIt = startDragZoom != null || selection.y1 <= hgTracks.rulerClickHeight;
     }
     if(doIt) {
         // startDragZoom is null if mouse has never been moved
         var singleClick = (selection.x2 == selection.x1) || startDragZoom == null || (now.getTime() - startDragZoom) < 100;
-        newPosition = updatePosition(img, selection, singleClick);
+        var newPosition = updatePosition(img, selection, singleClick);
 	if(newPosition != undefined) {
             if(inPlaceUpdate) {
-                navigateInPlace("position=" + newPosition, null);
+                navigateInPlace("position=" + newPosition, null, true);
             } else {
                 jQuery('body').css('cursor', 'wait');
 	        document.TrackHeaderForm.submit();
             }
 	}
     } else {
         setPosition(originalPosition, originalSize);
         originalPosition = originalSize = null;
 //        if(mapHtml) {
 //            $('#map').append(mapHtml);
 //        }
     }
 //    mapHtml = null;
     startDragZoom = null;
     setTimeout('blockUseMap=false;',50); // Necessary incase the selectEnd was over a map item. select takes precedence.
@@ -744,32 +746,30 @@
             begX = down + img.left;
             wide = (cur - down);
         }
         $(hilite).css({ left: begX + 'px', width: wide + 'px', top: topY + 'px', height: high + 'px', display:'' });
         $(hilite).show();
     }
     function hiliteCancel(left,width,top,height)
     {   // Called on mouseup: Make green drag hilite disappear when no longer wanted
         $(hilite).hide();
         $(hilite).css({ left: '0px', width: '0px', top: '0px', height: '0px' });
     }
 
     function hiliteSetup()
     {   // Called on init: setup of drag region hilite (but don't show yet)
         $(hilite).css({ backgroundColor: 'green', opacity: 0.4, borderStyle: 'solid', borderWidth: '1px', bordercolor: '#0000FF' });
-        $p = $(chrImg);
-
         $(hilite).css({ display: 'none', position: 'absolute', overflow: 'hidden', zIndex: 1 });
         jQuery($(chrImg).parents('body')).append($(hilite));
         return hilite;
     }
 
     function updateImgOffsets()
     {   // Called on mousedown: Gets the current offsets
         var offs = $(chrImg).offset();
         img.top  = Math.round(offs.top );
         img.left = Math.round(offs.left);
         img.scrolledTop  = img.top  - $("body").scrollTop();
         img.scrolledLeft = img.left - $("body").scrollLeft();
         if($.browser.msie) {
             img.height = $(chrImg).outerHeight();
             img.width  = $(chrImg).outerWidth();
@@ -1174,31 +1174,31 @@
             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), null);
+                    navigateInPlace("position=" + encodeURIComponent(pos.chrom + ":" + pos.start + "-" + pos.end), null, true);
                 } 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) {
                 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";
                 }
@@ -1670,30 +1670,31 @@
     }
 }
 
 function mapItemMouseOver()
 {
     // Record data for current map area item
     currentMapItem = makeMapItem(this.id);
     if(currentMapItem != null) {
         currentMapItem.href = this.href;
         currentMapItem.title = this.title;
     }
 }
 
 function mapItemMouseOut(obj)
 {
+    lastMapItem = currentMapItem;
     currentMapItem = null;
 }
 
 function findMapItem(e)
 {
 // Find mapItem for given event; returns item object or null if none found.
 
     if(currentMapItem) {
         return currentMapItem;
     }
 
     // rightClick for non-map items that can be resolved to their parent tr and then trackName (e.g. items in gray bar)
     if(e.target.tagName.toUpperCase() != "AREA") {
         var tr = $( e.target ).parents('tr.imgOrd');
         if( $(tr).length == 1 ) {
@@ -1949,31 +1950,31 @@
         $.ajax({
                    type: "GET",
                    url: "../cgi-bin/hgTracks",
                    data: data,
                    dataType: "html",
                    trueSuccess: handleViewImg,
                    success: catchErrorOrDispatch,
                    error: errorHandler,
                    cmd: cmd,
                    cache: false
                });
     } else if (cmd == 'openLink' || cmd == 'followLink') {
         var href = selectedMenuItem.href;
         var vars = new Array("c", "l", "r", "db");
         var valNames = new Array("chromName", "winStart", "winEnd");
-        for (i in vars) {
+        for (var i in vars) {
             // make sure the link contains chrom and window width info (necessary b/c we are stripping hgsid and/or the cart may be empty);
             // but don't add chrom to wikiTrack links (see redmine #2476).
             var v = vars[i];
             var val;
             if(v == "db") {
                 val = getDb();
             } else {
                 val = hgTracks[valNames[i]];
             }
             if(val && id != "wikiTrack" && (href.indexOf("?" + v + "=") == -1) && (href.indexOf("&" + v + "=") == -1)) {
                 href = href + "&" + v + "=" + val;
             }
         }
         if(cmd == 'followLink') {
             // XXXX This is blocked by Safari's popup blocker (without any warning message).
@@ -2209,31 +2210,31 @@
                     if(cur) {
                         $(select).children().each(function(index, o) {
                                                 var title = $(this).val();
                                                 var str = blankImg + " " + title;
                                                 if(title == cur)
                                                     str = selectedImg + " " + title;
                                                 var o = new Object();
                                                 o[str] = {onclick: function (menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, title); return true;}};
                                                 menu.push(o);
                                             });
                         done = true;
                     } else {
                         if(rec) {
                             // XXXX check current state from a hidden variable.
                             var visibilityStrs = new Array("hide", "dense", "squish", "pack", "full");
-                            for (i in visibilityStrs) {
+                            for (var i in visibilityStrs) {
                                 // XXXX use maxVisibility and change hgTracks so it can hide subtracks
                                 var o = new Object();
                                 var str = blankImg + " " + visibilityStrs[i];
                                 if(rec.canPack || (visibilityStrs[i] != "pack" && visibilityStrs[i] != "squish")) {
                                     if(rec.localVisibility) {
                                         if(visibilityStrs[i] == rec.localVisibility) {
                                             str = selectedImg + " " + visibilityStrs[i];
                                         }
                                     } else if(visibilityStrs[i] == visibilityStrsOrder[rec.visibility]) {
                                         str = selectedImg + " " + visibilityStrs[i];
                                     }
                                     o[str] = {onclick: makeContextMenuHitCallback(visibilityStrs[i])};
                                     menu.push(o);
                                 }
                             }
@@ -2758,38 +2759,38 @@
           }
     } else {
         if(imageV2) {
             // Implement in-place updating of hgTracks image
             setPositionByCoordinates(json.chromName, json.winStart + 1, json.winEnd);
             $("input[name='c']").val(json.chromName);
             $("input[name='l']").val(json.winStart);
             $("input[name='r']").val(json.winEnd);
             if(json.cgiVersion != hgTracks.cgiVersion) {
                 // Must reload whole page because of a new version on the server; this should happen very rarely.
                 // Note that we have already updated position based on the user's action.
                 jQuery('body').css('cursor', 'wait');
 	        document.TrackHeaderForm.submit();
             } else {
                 // We update rows one at a time (updating the whole imgTable at one time doesn't work in IE).
-                for (id in hgTracks.trackDb) {
+                for (var id in hgTracks.trackDb) {
                     if(hgTracks.trackDb[id].type != "remote" && !updateTrackImgForId(response, id)) {
                         showWarning("Couldn't parse out new image for id: " + id);
                         //alert("Couldn't parse out new image for id: " + id+"BR"+response);  // Very helpful
                     }
                 }
 /* This (disabled) code handles dynamic addition of tracks:
-                for (id in hgTracks.trackDb) {
+                for (var id in hgTracks.trackDb) {
                     if(oldTrackDb[id] == undefined) {
                         // XXXX Tim, what s/d abbr attribute be?
                         $('#imgTbl').append("<tr id='tr_" + id + "' class='imgOrd trDraggable'></tr>");
                         updateTrackImgForId(response, id);
                         updateVisibility(id, visibilityStrsOrder[hgTracks.trackDb[id].visibility]);
                     }
                 }
 */
                 hgTracks = json;
                 originalPosition = undefined;
                 initVars();
                 afterImgTblReload();
             }
         } else {
             a= /<IMG([^>]+SRC[^>]+id='trackMap[^>]*)>/.exec(response);
@@ -2844,30 +2845,34 @@
         b = /SRC\s*=\s*"([^")]+)"/.exec(a[1]);
         if(b[1]) {
             $('#chrom').attr('src', b[1]);
         }
     }
     if(hgTracks.measureTiming) {
         updateTiming(response);
     }
     if(this.disabledEle) {
         this.disabledEle.attr('disabled', '');
     }
     if(this.loadingId) {
         hideLoadingImage(this.loadingId);
     }
     jQuery('body').css('cursor', '');
+    if(this.currentId) {
+        var top = $("#tr_" + this.currentId).position().top;
+        $(window).scrollTop(top - this.currentIdYOffset);
+    }
 }
 
 function handleViewImg(response, status)
 { // handles view image response, which must get new image without imageV2 gimmickery
     jQuery('body').css('cursor', '');
     var str = "<IMG[^>]*SRC='([^']+)'";
     var reg = new RegExp(str);
     a = reg.exec(response);
     if(a && a[1]) {
         if(window.open(a[1]) == null) {
             windowOpenFailedMsg();
         }
         return;
     }
     showWarning("Couldn't parse out img src");
@@ -3006,71 +3011,86 @@
 }
 
 function navigateButtonClick(ele)
 {
 // code to update just the imgTbl in response to navigation buttons (zoom-out etc.).
 // This is currently experimental code (controlled by IN_PLACE_UPDATE in imageV2.h).
     if(inPlaceUpdate) {
         var params = ele.name + "=" + ele.value;
         $(ele).attr('disabled', 'disabled');
         // dinking navigation needs additional data
         if(ele.name == "hgt.dinkLL" || ele.name == "hgt.dinkLR") {
             params += "&dinkL=" + $("input[name='dinkL']").val();
         } else if(ele.name == "hgt.dinkRL" || ele.name == "hgt.dinkRR") {
             params += "&dinkR=" + $("input[name='dinkR']").val();
         }
-        navigateInPlace(params, $(ele));
+        navigateInPlace(params, $(ele), false);
         return false;
     } else {
         return true;
     }
 }
 
-function navigateInPlace(params, disabledEle)
+function navigateInPlace(params, disabledEle, keepCurrentTrackVisible)
 {
 // request an hgTracks image, using params
 // disabledEle is optional; this element will be enabled when update is complete
+// If keepCurrentTrackVisible is true, we try to maintain relative position of the item under the mouse after the in-place update.
     jQuery('body').css('cursor', '');
+    var currentId, currentIdYOffset;
+    if(keepCurrentTrackVisible) {
+        var item = currentMapItem || lastMapItem;
+        if(item) {
+            var top = $("#tr_" + item.id).position().top;
+            if(top >= $(window).scrollTop() || top < $(window).scrollTop() + $(window).height()) {
+                // don't bother if the item is not currently visible.
+                currentId = item.id;
+                currentIdYOffset = top - $(window).scrollTop();
+            }
+        }
+    }
     $.ajax({
                type: "GET",
                url: "../cgi-bin/hgTracks",
                data: params + "&hgt.trackImgOnly=1&hgt.ideogramToo=1&hgsid=" + getHgsid(),
                dataType: "html",
                trueSuccess: handleUpdateTrackMap,
                success: catchErrorOrDispatch,
                error: errorHandler,
                cmd: 'wholeImage',
                loadingId: showLoadingImage("imgTbl"),
                disabledEle: disabledEle,
+               currentId: currentId,
+               currentIdYOffset: currentIdYOffset,
                cache: false
            });
 }
 
 function updateButtonClick(ele)
 {
 // code to update the imgTbl based on changes in the track controls.
 // This is currently experimental code and is dead in the main branch.
     if(mapIsUpdateable) {
         var data = "";
         $("select").each(function(index, o) {
                                                var cmd = $(this).val();
                                                if(cmd == "hide") {
                                                      if(hgTracks.trackDb[this.name] != undefined) {
                                                          alert("Need to implement hide");
                                                      }
                                                } else {
                                                      if(hgTracks.trackDb[this.name] == undefined || cmd != visibilityStrsOrder[hgTracks.trackDb[this.name].visibility]) {
                                                         if(data.length > 0) {
                                                              data = data + "&";
                                                         }
                                                         data = data + this.name + "=" + cmd;
                                                      }
                                                }
                                             });
         if(data.length > 0) {
-            navigateInPlace(data, null);
+            navigateInPlace(data, null, false);
         }
         return false;
     } else {
         return true;
     }
 }