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; } }