13945005698c2779fe67a58decf79b3121efbab9
galt
Fri Dec 12 17:38:15 2014 -0800
Fixes 14509. hgTracks.js had an incorrect assumption that the position to be updated in the document title would always be in the 2nd position after parsing on spaces. But "D. melanogaster" and several others which used the scientific name would break. It was tricky because it could get fixed by certain refreshes. The new hgTracks.js needs to get pushed with this release. The fix involves simply scanning for a word that contains a colon character which should only appear in the position field, and does not appear in dbDb names or sci-names.
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 978e58a..8377efa 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -1,3778 +1,3782 @@
// Javascript for use in hgTracks CGI
// "use strict";
// Don't complain about line break before '||' etc:
/* jshint -W014 */
var debug = false;
/* 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 revCmplDisp // true if we are in reverse display
* int insideX // width of side-bar (in pixels)
* int rulerClickHeight // height of ruler (in pixels) - zero if ruler is hidden
* boolean inPlaceUpdate // true if in-place-update is turned on
* int imgBox* // various drag-scroll values
* boolean measureTiming // true if measureTiming is on
* Object trackDb // hash of trackDb entries for tracks which are visible on current page
*/
function initVars()
{ // There are various entry points, so we call initVars in several places to make sure all is well
if (typeof(hgTracks) !== "undefined" && !genomePos.original) {
// remember initial position and size so we can restore it if user cancels
genomePos.original = genomePos.getOriginalPos();
genomePos.originalSize = $('#size').text();
dragSelect.originalCursor = jQuery('body').css('cursor');
imageV2.imgTbl = $('#imgTbl');
// imageV2.enabled === true unless: advancedJavascript===false, or trackSearch, or config pg
imageV2.enabled = (imageV2.imgTbl && imageV2.imgTbl.length > 0);
// jQuery load function with stuff to support drag selection in track img
if (theClient.isSafari()) {
// Safari has the following bug: if we update the hgTracks map dynamically,
// the browser ignores the changes (even though if you look in the DOM the changes
// are there). So we have to do a full form submission when the user changes
// visibility settings or track configuration.
// As of 5.0.4 (7533.20.27) this is problem still exists in safari.
// As of 5.1 (7534.50) this problem appears to have been fixed - unfortunately,
// logs for 7/2011 show vast majority of safari users are pre-5.1 (5.0.5 is by far
// the most common).
//
// Early versions of Chrome had this problem too, but this problem went away
// as of Chrome 5.0.335.1 (or possibly earlier).
imageV2.mapIsUpdateable = false;
var reg = new RegExp("Version\/([0-9]+.[0-9]+) Safari");
var a = reg.exec(navigator.userAgent);
if (a && a[1]) {
var version = Number(a[1]);
if (version >= 5.1) {
imageV2.mapIsUpdateable = true;
}
}
}
imageV2.inPlaceUpdate = hgTracks.inPlaceUpdate && imageV2.mapIsUpdateable;
}
}
/////////////////////////////////////
////////// Genomic position /////////
/////////////////////////////////////
var genomePos = {
original: null,
originalSize: 0,
linkFixup: function (pos, id, reg, endParamName)
{ // fixup external links (e.g. ensembl)
var ele = $(document.getElementById(id));
if (ele.length) {
var link = ele.attr('href');
var a = reg.exec(link);
if (a && a[1]) {
ele.attr('href', a[1] + pos.start + "&" + endParamName + "=" + pos.end);
}
}
},
setByCoordinates: function (chrom, start, end)
{
var newPosition = chrom + ":" + commify(start) + "-" + commify(end);
genomePos.set(newPosition, commify(end - start + 1));
return newPosition;
},
getElement: function ()
{
// Return position box object
var tags = document.getElementsByName("position");
// There are multiple tags with name === "position" (the visible position text input
// and a hidden with id='positionHidden'); we return value of visible element.
for (var i = 0; i < tags.length; i++) {
var ele = tags[i];
if (ele.id !== "positionHidden") {
return ele;
}
}
return null;
},
get: function ()
{
// Return current value of position box
var ele = genomePos.getElement();
if (ele) {
return ele.value;
}
return null;
},
getOriginalPos: function ()
{
return genomePos.original || genomePos.get();
},
revertToOriginalPos: function ()
{
// undo changes to position (i.e. after user aborts a drag-and-select).
this.set(this.original, this.originalSize);
this.original = this.originalSize = null; // not sure if this is necessary.
},
set: function (position, size)
{ // Set value of position and size (in hiddens and input elements).
// We assume size has already been commified.
// Either position or size may be null.
if (position) {
// There are multiple tags with name === "position"
// (one in TrackHeaderForm and another in TrackForm).
var tags = document.getElementsByName("position");
for (var i = 0; i < tags.length; i++) {
var ele = tags[i];
ele.value = position;
}
}
if ($('#positionDisplay').length) {
$('#positionDisplay').text(position);
}
if (size) {
$('#size').text(size);
}
var pos = parsePosition(position);
if (pos) {
// fixup external static links on page'
// Example ensembl link:
// http://www.ensembl.org/Homo_sapiens/contigview?chr=21&start=33031934&end=33041241
genomePos.linkFixup(pos, "ensemblLink", new RegExp("(.+start=)[0-9]+"), "end");
// Example NCBI link:
// http://www.ncbi.nlm.nih.gov/mapview/maps.cgi?taxid=9606&CHR=21&BEG=33031934&END=33041241
genomePos.linkFixup(pos, "ncbiLink", new RegExp("(.+BEG=)[0-9]+"), "END");
// Example medaka link:
// http://utgenome.org/medakabrowser_ens_jump.php?revision=version1.0&chr=chromosome18&start=14435198&end=14444829
genomePos.linkFixup(pos, "medakaLink", new RegExp("(.+start=)[0-9]+"), "end");
var link;
var reg;
var a;
if ($('#wormbaseLink').length) {
// e.g. http://www.wormbase.org/db/gb2/gbrowse/c_elegans?name=II:14646301-14667800
link = $('#wormbaseLink').attr('href');
reg = new RegExp("(.+:)[0-9]+");
a = reg.exec(link);
if (a && a[1]) {
$('#wormbaseLink').attr('href', a[1] + pos.start + "-" + pos.end);
}
}
// Fixup DNA link; e.g.:
// hgc?hgsid=2999470&o=114385768&g=getDna&i=mixed&c=chr7&l=114385768&r=114651696&db=panTro2&hgsid=2999470
if ($('#dnaLink').length) {
link = $('#dnaLink').attr('href');
reg = new RegExp("(.+&o=)[0-9]+.+&db=[^&]+(.*)");
a = reg.exec(link);
if (a && a[1]) {
var url = a[1] + (pos.start - 1) + "&g=getDna&i=mixed&c=" + pos.chrom;
url += "&l=" + (pos.start - 1) + "&r=" + pos.end + "&db=" + getDb() + a[2];
$('#dnaLink').attr('href', url);
}
}
}
if (!imageV2.backSupport)
imageV2.markAsDirtyPage();
},
check: function (img, selection)
{ // return true if user's selection is still w/n the img (including some slop).
var imgWidth = jQuery(img).width();
var imgHeight = jQuery(img).height();
var imgOfs = jQuery(img).offset();
var slop = 10;
// We ignore clicks in the gray tab and track title column
// (we really should suppress all drag activity there,
// but I don't know how to do that with imgAreaSelect).
var leftX = hgTracks.revCmplDisp ? imgOfs.left - slop :
imgOfs.left + hgTracks.insideX - slop;
var rightX = hgTracks.revCmplDisp ? imgOfs.left + imgWidth - hgTracks.insideX + slop :
imgOfs.left + imgWidth + slop;
return ( (selection.event.pageX >= leftX)
&& (selection.event.pageX < rightX)
&& (selection.event.pageY >= (imgOfs.top - slop))
&& (selection.event.pageY < (imgOfs.top + imgHeight + slop)));
},
pixelsToBases: function (img, selStart, selEnd, winStart, winEnd)
{ // Convert image coordinates to chromosome coordinates
var imgWidth = jQuery(img).width() - hgTracks.insideX;
var width = hgTracks.winEnd - hgTracks.winStart;
var mult = width / imgWidth; // mult is bp/pixel multiplier
var startDelta; // startDelta is how many bp's to the right/left
var x1;
if (hgTracks.revCmplDisp) {
x1 = Math.min(imgWidth, selStart);
startDelta = Math.floor(mult * (imgWidth - x1));
} else {
x1 = Math.max(hgTracks.insideX, selStart);
startDelta = Math.floor(mult * (x1 - hgTracks.insideX));
}
var endDelta;
var x2;
if (hgTracks.revCmplDisp) {
endDelta = startDelta;
x2 = Math.min(imgWidth, selEnd);
startDelta = Math.floor(mult * (imgWidth - x2));
} else {
x2 = Math.max(hgTracks.insideX, selEnd);
endDelta = Math.floor(mult * (x2 - hgTracks.insideX));
}
var newStart = hgTracks.winStart + startDelta;
var newEnd = hgTracks.winStart + 1 + endDelta;
if (newEnd > winEnd) {
newEnd = winEnd;
}
return {chromStart : newStart, chromEnd : newEnd};
},
selectionPixelsToBases: function (img, selection)
{ // Convert selection x1/x2 coordinates to chromStart/chromEnd.
return genomePos.pixelsToBases(img, selection.x1, selection.x2,
hgTracks.winStart, hgTracks.winEnd);
},
update: function (img, selection, singleClick)
{
var pos = genomePos.pixelsToBases(img, selection.x1, selection.x2,
hgTracks.winStart, hgTracks.winEnd);
// singleClick is true when the mouse hasn't moved (or has only moved a small amount).
if (singleClick) {
var center = (pos.chromStart + pos.chromEnd)/2;
pos.chromStart = Math.floor(center - hgTracks.newWinWidth/2);
pos.chromEnd = pos.chromStart + hgTracks.newWinWidth;
}
var newPosition = genomePos.setByCoordinates(hgTracks.chromName,
pos.chromStart+1, pos.chromEnd);
return newPosition;
},
handleChange: function (response, status)
{
var json = JSON.parse(response);
genomePos.set(json.pos);
},
changeAssemblies: function (ele) // UNUSED? Larry's experimental code
{ // code to update page when user changes assembly select list.
$.ajax({
type: "GET",
url: "../cgi-bin/hgApi",
data: cart.varsToUrlData({ 'cmd': 'defaultPos', 'db': getDb() }),
dataType: "html",
trueSuccess: genomePos.handleChange,
success: catchErrorOrDispatch,
error: errorHandler,
cache: true
});
return false;
}
};
/////////////////////////////////////
//// Creating items by dragging /////
/////////////////////////////////////
var makeItemsByDrag = {
end: function (img, selection)
{
var image = $(img);
var imageId = image.attr('id');
var trackName = imageId.substring('img_data_'.length);
var pos = genomePos.selectionPixelsToBases(image, selection);
var command = document.getElementById('hgt_doJsCommand');
command.value = "makeItems " + trackName + " " + hgTracks.chromName;
command.value += " " + pos.chromStart + " " + pos.chromEnd;
document.TrackHeaderForm.submit();
return true;
},
init: function (trackName)
{
// Set up so that they can drag out to define a new item on a makeItems track.
var img = $("#img_data_" + trackName);
if (img && img.length !== 0) {
var imgHeight = imageV2.imgTbl.height();
jQuery(img.imgAreaSelect( { selectionColor: 'green', outerColor: '',
minHeight: imgHeight, maxHeight: imgHeight, onSelectEnd: makeItemsByDrag.end,
autoHide: true, movable: false}));
}
},
load: function ()
{
for (var id in hgTracks.trackDb) {
var rec = hgTracks.trackDb[id];
if (rec && rec.type && rec.type.indexOf("makeItems") === 0) {
this.init(id);
}
}
}
};
///////////////////
///// mouse /////
///////////////////
var mouse = {
savedOffset: {x:0, y:0},
saveOffset: function (ev)
{ // Save the mouse offset associated with this event
mouse.savedOffset = {x: ev.clientX, y: ev.clientY};
},
hasMoved: function (ev)
{ // return true if mouse has moved a significant amount
var minPixels = 10;
var movedX = ev.clientX - mouse.savedOffset.x;
var movedY = ev.clientY - mouse.savedOffset.y;
if (arguments.length === 2) {
var num = Number(arguments[1]);
if (isNaN(num)) {
if ( arguments[1].toLowerCase() === "x" )
return (movedX > minPixels || movedX < (minPixels * -1));
if ( arguments[1].toLowerCase() === "y" )
return (movedY > minPixels || movedY < (minPixels * -1));
}
else
minPixels = num;
}
return ( movedX > minPixels || movedX < (minPixels * -1)
|| movedY > minPixels || movedY < (minPixels * -1));
}
};
/////////////////
//// posting ////
/////////////////
var posting = {
blockUseMap: false,
blockMapClicks: function ()
// Blocks clicking on map items when in effect. Drag opperations frequently call this.
{
posting.blockUseMap=true;
},
allowMapClicks:function ()
// reallows map clicks. Called after operations that compete with clicks (e.g. dragging)
{
$('body').css('cursor', ''); // Explicitly remove wait cursor.
posting.blockUseMap=false;
},
mapClicksAllowed: function ()
// Verify that click-competing operation (e.g. dragging) isn't currently occurring.
{
return (posting.blockUseMap === false);
},
blockTheMapOnMouseMove: function (ev)
{
if (!posting.blockUseMap && mouse.hasMoved(ev)) {
posting.blockUseMap=true;
}
},
mapClk: function ()
{
var done = false;
if (false && imageV2.inPlaceUpdate) {
// XXXX experimental and only turned on in larrym's tree.
// Use in-place update if the map item just modifies the current position (this is nice
// because it's faster and preserves the users relative position in the track image).
//
// First test handles next/prev item.
var str = "/cgi-bin/hgTracks\\?position=([^:]+):(.+)&hgsid=(\\d+)" +
"&(hgt.(next|prev)Item=[^&]+)";
var reg = new RegExp(str);
var a = reg.exec(this.href);
if (a && a[1] && a[1] === hgTracks.chromName) {
imageV2.navigateInPlace("position=" + encodeURIComponent(a[1] + ":" + a[2]) +
"&" + a[4], null, true);
done = true;
} else {
// handle next/prev exon
str = "/cgi-bin/hgTracks\\?position=([^:]+):(.+)&hgsid=(\\d+)$";
reg = new RegExp(str);
a = reg.exec(this.href);
if (a && a[1]) {
imageV2.navigateInPlace("position=" + encodeURIComponent(a[1] + ":" + a[2]),
null, true);
done = true;
} else {
// handle toggle visibility.
// Request may include a track set, so we cannot use requestImgUpdate.
str = "/cgi-bin/hgTracks\\?(position=[^:]+:.+&hgsid=\\d+&([^=]+)=([^&]+))$";
reg = new RegExp(str);
a = reg.exec(this.href);
if (a && a[1]) {
imageV2.navigateInPlace(a[1], null, true);
// imageV2.requestImgUpdate(a[1], a[1] + "=" + a[2], "", a[2]);
done = true;
}
}
}
}
if (done)
return false;
else
return posting.saveSettings(this);
},
saveSettings: function (obj)
{
if (posting.blockUseMap === true) {
return false;
}
if (!obj || !obj.href) // called directly with obj
obj = this; // and from callback without obj
if ($(obj).hasClass('noLink')) // TITLE_BUT_NO_LINK
return false;
if (obj.href.match('#') || obj.target.length > 0) {
//alert("Matched # ["+obj.href+"] or has target:"+obj.target);
return true;
}
var thisForm = normed($(obj).parents('form').first());
if (!thisForm)
thisForm = normed($("FORM").first());
if (thisForm) {
//alert("posting form:"+$(thisForm).attr('name'));
return postTheForm($(thisForm).attr('name'),cart.addUpdatesToUrl(obj.href));
}
return true;
}
};
/////////////////////////
//// cart updating /////
///////////////////////
var cart = {
// Controls queuing and ultimately updating cart variables vis ajax or submit. Queued vars
// are held in an object with unique keys preventing duplicate updates and ensuring last update
// takes precedence. WARNING: be careful creating an object with variables on the fly:
// cart.setVarsObj({track: vis}) is invalid but cart.setVarsObj({'knownGene': vis}) is ok!
updateQueue: {},
updatesWaiting: function ()
{ // returns TRUE if updates are waiting.
return objNotEmpty(cart.updateQueue);
},
addUpdatesToUrl: function (url)
{ // adds any outstanding cart updates to the url, then clears the queue
if (cart.updatesWaiting()) {
//console.log('cart.addUpdatesToUrl: '+objKeyCount(cart.updateQueue)+' vars');
var updates = cart.varsToUrlData(); // clears the queue
if (!url || url.length === 0)
return updates;
if (updates.length > 0) {
var dataOnly = (url.indexOf("cgi-bin") === -1); // all urls should be to our cgis
if (!dataOnly && url.lastIndexOf("?") === -1)
url += "?" + updates;
else
url += '&' + updates;
}
}
return url;
},
beforeUnload: function ()
{ // named function that can be bound and unbound to beforeunload event
// Makes sure any outstanding queued updates are sent before leaving the page.
//console.log('cart.beforeUnload: '+objKeyCount(cart.updateQueue)+' vars');
cart.setVarsObj( {}, null, false ); // synchronous
},
varsToUrlData: function (varsObj)
{ // creates a url data (var1=val1&var2=val2...) string from vars object and queue
// The queue will be emptied by this call.
cart.queueVarsObj(varsObj); // lay ontop of queue, to give new values precedence
// Now convert to url data and clear queue
var urlData = '';
if (cart.updatesWaiting()) {
//console.log('cart.varsToUrlData: '+objKeyCount(cart.updateQueue)+' vars');
urlData = varHashToQueryString(cart.updateQueue);
cart.updateQueue = {};
}
return urlData;
},
setVarsObj: function (varsObj, errFunc, async)
{ // Set all vars in a var hash, appending any queued updates
//console.log('cart.setVarsObj: were:'+objKeyCount(cart.updateQueue) +
// ' new:'+objKeyCount(varsObj);
cart.queueVarsObj(varsObj); // lay ontop of queue, to give new values precedence
// Now ajax update all in queue and clear queue
if (cart.updatesWaiting()) {
setVarsFromHash(cart.updateQueue, errFunc, async);
cart.updateQueue = {};
}
},
setVars: function (names, values, errFunc, async)
{ // ajax updates the cart, and includes any queued updates.
cart.setVarsObj(arysToObj(names, values), errFunc, async);
},
queueVarsObj: function (varsObj)
{ // Add object worth of cart updates to the 'to be updated' queue, so they can be sent to
// the server later. Note: hash allows overwriting previous updates to the same variable.
if (typeof varsObj !== 'undefined' && objNotEmpty(varsObj)) {
//console.log('cart.queueVarsObj: were:'+objKeyCount(cart.updateQueue) +
// ' new:'+objKeyCount(varsObj));
for (var name in varsObj) {
cart.updateQueue[name] = varsObj[name];
// Could update in background, however, failing to hit "refresh" is user choice
// first in queue, schedule background update
if (objKeyCount(cart.updateQueue) === 1) {
// By unbind/bind, we assure that there is only one instance bound
$(window).unbind('beforeunload', cart.beforeUnload);
$(window).bind( 'beforeunload', cart.beforeUnload);
}
}
}
},
addVarsToQueue: function (names,values)
{ // creates a string of updates to save for ajax batch or a submit
cart.queueVarsObj(arysToObj(names,values));
}
};
///////////////////////////////////////////////
//// visibility (mixed with group toggle) /////
///////////////////////////////////////////////
var vis = {
// map cgi enum visibility codes to strings
enumOrder: new Array("hide", "dense", "full", "pack", "squish"),
update: function (track, visibility)
{ // Updates visibility state in hgTracks.trackDb and any visible elements on the page.
// returns true if we modify at least one select in the group list
var rec = hgTracks.trackDb[track];
var selectUpdated = false;
$("select[name=" + escapeJQuerySelectorChars(track) + "]").each(function(t) {
$(this).attr('class', visibility === 'hide' ? 'hiddenText' : 'normalText');
$(this).val(visibility);
selectUpdated = true;
});
if (rec) {
rec.localVisibility = visibility;
}
return selectUpdated;
},
get: function (track)
{ // return current visibility for given track
var rec = hgTracks.trackDb[track];
if (rec) {
if (rec.localVisibility) {
return rec.localVisibility;
} else {
return vis.enumOrder[rec.visibility];
}
} else {
return null;
}
},
makeTrackVisible: function (track)
{ // Sets the vis box to visible, and ques a cart update, but does not update the image
if (track && vis.get(track) !== "full") {
vis.update(track, 'pack');
cart.addVarsToQueue([track], ['pack']);
}
},
toggleForGroup: function (button, prefix)
{ // toggle visibility of a track group; prefix is the prefix of all the id's of tr's in the
// relevant group. This code also modifies the corresponding hidden fields and the gif
// of the +/- img tag.
imageV2.markAsDirtyPage();
if (arguments.length > 2)
return setTableRowVisibility(button, prefix, "hgtgroup", "group",false,arguments[2]);
else
return setTableRowVisibility(button, prefix, "hgtgroup", "group",false);
},
expandAllGroups: function (newState)
{ // Set visibility of all track groups to newState (true means expanded).
// This code also modifies the corresponding hidden fields and the gif's of the +/- img tag.
imageV2.markAsDirtyPage();
$(".toggleButton[id$='_button']").each( function (i) {
// works for old img type AND new BUTTONS_BY_CSS // - 7: clip '_button' suffix
vis.toggleForGroup(this,this.id.substring(0,this.id.length - 7),newState);
});
return false;
},
initForAjax: function()
{ // To better support the back-button, it is good to eliminate extraneous form puts
// Towards that end, we support visBoxes making ajax calls to update cart.
var sels = $('select.normalText,select.hiddenText');
$(sels).change(function() {
var track = $(this).attr('name');
if ($(this).val() === 'hide') {
var rec = hgTracks.trackDb[track];
if (rec)
rec.visibility = 0;
// else Would be nice to hide subtracks as well but that may be overkill
$(document.getElementById('tr_' + track)).remove();
imageV2.highlightRegion();
$(this).attr('class', 'hiddenText');
} else
$(this).attr('class', 'normalText');
cart.addVarsToQueue([track], [$(this).val()]);
imageV2.markAsDirtyPage();
return false;
});
// Now we can rid the submt of the burden of all those vis boxes
var form = $('form#TrackForm');
$(form).submit(function () {
$('select.normalText,select.hiddenText').attr('disabled',true);
});
$(form).attr('method','get');
},
restoreFromBackButton: function()
// Re-enabling vis dropdowns is necessarty because intiForAjax() disables them on submit.
{
$('select.normalText,select.hiddenText').attr('disabled',false);
}
};
////////////////////////////////////////////////////////////
// dragSelect is also known as dragZoom or shift-dragZoom //
////////////////////////////////////////////////////////////
var dragSelect = {
areaSelector: null, // formerly "imgAreaSelect". jQuery element used for imgAreaSelect
originalCursor: null,
startTime: null,
selectStart: function (img, selection)
{
initVars();
if (rightClick.menu) {
rightClick.menu.hide();
}
var now = new Date();
dragSelect.startTime = now.getTime();
posting.blockMapClicks();
},
selectChange: function (img, selection)
{
if (selection.x1 !== selection.x2) {
if (genomePos.check(img, selection)) {
genomePos.update(img, selection, false);
jQuery('body').css('cursor', dragSelect.originalCursor);
} else {
jQuery('body').css('cursor', 'not-allowed');
}
}
return true;
},
highlightThisRegion: function(newPosition)
// set highlighting newPosition in server-side cart and apply the highlighting in local UI.
{
var start, end;
if (arguments.length === 2) {
start = arguments[0];
end = arguments[1];
} else {
var pos = parsePosition(newPosition);
start = pos.start;
end = pos.end;
}
hgTracks.highlight = getDb() + "." + hgTracks.chromName + ":" + start + "-" + end;
hgTracks.highlight += '#AAFFFF'; // Also include highlight color
// we include enableHighlightingDialog because it may have been changed by the dialog
cart.setVarsObj({ 'highlight': hgTracks.highlight,
'enableHighlightingDialog': hgTracks.enableHighlightingDialog ? 1 : 0 });
imageV2.highlightRegion();
},
selectionEndDialog: function (newPosition)
// Let user choose between zoom-in and highlighting.
{
var dragSelectDialog = $("#dragSelectDialog")[0];
if (!dragSelectDialog) {
$("body").append("
" + newPosition +
"
" +
"Don't show this dialog again and always zoom. " +
"(Re-enable highlight via the 'configure' menu at any time.)
");
dragSelectDialog = $("#dragSelectDialog")[0];
}
$(dragSelectDialog).dialog({
modal: true,
title: "Drag-and-select",
closeOnEscape: true,
resizable: false,
autoOpen: false,
revertToOriginalPos: true,
minWidth: 400,
buttons: {
"Zoom In": function() {
// Zoom to selection
$(this).dialog("option", "revertToOriginalPos", false);
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
if (imageV2.inPlaceUpdate) {
var params = "position=" + newPosition;
if (!hgTracks.enableHighlightingDialog)
params += "&enableHighlightingDialog=0";
imageV2.navigateInPlace(params, null, true);
} else {
$('body').css('cursor', 'wait');
if (!hgTracks.enableHighlightingDialog)
cart.setVarsObj({'enableHighlightingDialog': 0 });
document.TrackHeaderForm.submit();
}
$(this).dialog("close");
},
"Highlight": function() {
// Highlight selection
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
dragSelect.highlightThisRegion(newPosition);
$(this).dialog("close");
},
"Cancel": function() {
$(this).dialog("close");
}
},
open: function () { // Make zoom the focus/default action
$(this).parents('.ui-dialog-buttonpane button:eq(0)').focus();
},
close: function() {
// All exits to dialog should go through this
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if ($(this).dialog("option", "revertToOriginalPos"))
genomePos.revertToOriginalPos();
if ($("#disableDragHighlight").attr('checked'))
$(this).remove();
else
$(this).hide();
$('body').css('cursor', ''); // Occasionally wait cursor got left behind
}
});
$(dragSelectDialog).dialog('open');
},
selectEnd: function (img, selection)
{
var now = new Date();
var doIt = false;
if (dragSelect.originalCursor)
jQuery('body').css('cursor', dragSelect.originalCursor);
// ignore releases outside of the image rectangle (allowing a 10 pixel slop)
if (genomePos.check(img, selection)) {
// ignore single clicks that aren't in the top of the image
// (this happens b/c the clickClipHeight test in dragSelect.selectStart
// doesn't occur when the user single clicks).
doIt = dragSelect.startTime !== null || selection.y1 <= hgTracks.rulerClickHeight;
}
if (doIt) {
// dragSelect.startTime is null if mouse has never been moved
var singleClick = ( (selection.x2 === selection.x1)
|| dragSelect.startTime === null
|| (now.getTime() - dragSelect.startTime) < 100);
var newPosition = genomePos.update(img, selection, singleClick);
if (newPosition) {
if (hgTracks.enableHighlightingDialog)
dragSelect.selectionEndDialog(newPosition);
else {
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if (imageV2.inPlaceUpdate) {
imageV2.navigateInPlace("position=" + newPosition, null, true);
} else {
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
}
}
}
} else {
$(imageV2.imgTbl).imgAreaSelect({hide:true});
genomePos.revertToOriginalPos();
}
dragSelect.startTime = null;
// blockMapClicks/allowMapClicks() is necessary if selectEnd was over a map item.
setTimeout(posting.allowMapClicks,50);
return true;
},
load: function (firstTime)
{
var imgHeight = 0;
if (imageV2.enabled)
imgHeight = imageV2.imgTbl.innerHeight() - 1; // last little bit makes border look ok
// No longer disable without ruler, because shift-drag still works
if (typeof(hgTracks) !== "undefined") {
if (hgTracks.rulerClickHeight === undefined || hgTracks.rulerClickHeight === null)
hgTracks.rulerClickHeight = 0; // will be zero if no ruler track
var heights = hgTracks.rulerClickHeight;
dragSelect.areaSelector = jQuery((imageV2.imgTbl).imgAreaSelect({
selectionColor: 'blue',
outerColor: '',
minHeight: imgHeight,
maxHeight: imgHeight,
onSelectStart: dragSelect.selectStart,
onSelectChange: dragSelect.selectChange,
onSelectEnd: dragSelect.selectEnd,
autoHide: false, // gets hidden after possible dialog
movable: false,
clickClipHeight: heights
}));
}
}
};
/////////////////////////////////////
//// Chrom Drag/Zoom/Expand code ////
/////////////////////////////////////
jQuery.fn.chromDrag = function(){
this.each(function(){
// Plan:
// mouseDown: determine where in map: convert to img location: pxDown
// mouseMove: flag drag
// mouseUp: if no drag, then create href centered on bpDown loc with current span
// if drag, then create href from bpDown to bpUp
// if ctrlKey then expand selection to containing cytoBand(s)
// Image dimensions all in pix
var img = { top: -1, scrolledTop: -1, height: -1, left: -1, scrolledLeft: -1, width: -1 };
// chrom Dimensions beg,end,size in bases, rest in pix
var chr = { name: "", reverse: false, beg: -1, end: -1, size: -1,
top: -1, bottom: -1, left: -1, right: -1, width: -1 };
var pxDown = 0; // pix X location of mouseDown
var chrImg = $(this);
var mouseIsDown = false;
var mouseHasMoved = false;
var hilite = null;
initialize();
function initialize(){
findDimensions();
if (chr.top === -1)
warn("chromIdeo(): failed to register "+this.id);
else {
hiliteSetup();
$('area.cytoBand').unbind('mousedown'); // Make sure this is only bound once
$('area.cytoBand').mousedown( function(e)
{ // mousedown on chrom portion of image only (map items)
updateImgOffsets();
pxDown = e.clientX - img.scrolledLeft;
var pxY = e.clientY - img.scrolledTop;
if (mouseIsDown === false
&& isWithin(chr.left,pxDown,chr.right) && isWithin(chr.top,pxY,chr.bottom)) {
mouseIsDown = true;
mouseHasMoved = false;
$(document).bind('mousemove',chromMove);
$(document).bind( 'mouseup', chromUp);
hiliteShow(pxDown,pxDown);
return false;
}
});
}
}
function chromMove(e)
{ // If mouse was down, determine if dragged, then show hilite
if ( mouseIsDown ) {
var pxX = e.clientX - img.scrolledLeft;
var relativeX = (pxX - pxDown);
if (mouseHasMoved || (mouseHasMoved === false && Math.abs(relativeX) > 2)) {
mouseHasMoved = true;
if (isWithin(chr.left,pxX,chr.right))
hiliteShow(pxDown,pxX);
else if (pxX < chr.left)
hiliteShow(pxDown,chr.left);
else
hiliteShow(pxDown,chr.right);
}
}
}
function chromUp(e)
{ // If mouse was down, handle final selection
$(document).unbind('mousemove',chromMove);
$(document).unbind('mouseup',chromUp);
chromMove(e); // Just in case
if (mouseIsDown) {
updateImgOffsets();
var bands;
var pxUp = e.clientX - img.scrolledLeft;
var pxY = e.clientY - img.scrolledTop;
if (isWithin(0,pxY,img.height)) { // within vertical range or else cancel
var selRange = { beg: -1, end: -1, width: -1 };
var dontAsk = true;
if (e.ctrlKey) {
bands = findCytoBand(pxDown,pxUp);
if (bands.end > -1) {
pxDown = bands.left;
pxUp = bands.right;
mouseHasMoved = true;
dontAsk = false;
selRange.beg = bands.beg;
selRange.end = bands.end;
hiliteShow(pxDown,pxUp);
}
}
else if (mouseHasMoved) {
// bounded by chrom dimensions: but must remain within image!
if (isWithin(-20,pxUp,chr.left))
pxUp = chr.left;
if (isWithin(chr.right,pxUp,img.width + 20))
pxUp = chr.right;
if ( isWithin(chr.left,pxUp,chr.right+1) ) {
selRange.beg = convertToBases(pxDown);
selRange.end = convertToBases(pxUp);
if (Math.abs(selRange.end - selRange.beg) < 20)
mouseHasMoved = false; // Drag so small: treat as simple click
else
dontAsk = false;
}
}
if (mouseHasMoved === false) { // Not else because small drag turns this off
hiliteShow(pxUp,pxUp);
var curWidth = hgTracks.winEnd - hgTracks.winStart;
// Notice that beg is based upon up position
selRange.beg = convertToBases(pxUp) - Math.round(curWidth/2);
selRange.end = selRange.beg + curWidth;
}
if (selRange.end > -1) {
// prompt, then submit for new position
selRange = rangeNormalizeToChrom(selRange,chr);
if (mouseHasMoved === false) { // Update highlight by converting bp back to pix
pxDown = convertFromBases(selRange.beg);
pxUp = convertFromBases(selRange.end);
hiliteShow(pxDown,pxUp);
}
//if ((selRange.end - selRange.beg) < 50000)
// dontAsk = true;
if (dontAsk
|| confirm("Jump to new position:\n\n"+chr.name+":"+commify(selRange.beg)+
"-"+commify(selRange.end)+" size:"+commify(selRange.width)) ) {
genomePos.setByCoordinates(chr.name, selRange.beg, selRange.end);
// Stop the presses :0)
$('area.cytoBand').mousedown( function(e) { return false; });
if (imageV2.backSupport) {
imageV2.navigateInPlace("position=" +
encodeURIComponent(genomePos.get().replace(/,/g,'')),null,true);
hiliteCancel();
} else
document.TrackHeaderForm.submit();
return true; // Make sure the setTimeout below is not called.
}
}
}
hiliteCancel();
setTimeout(posting.allowMapClicks,50);
}
mouseIsDown = false;
mouseHasMoved = false;
}
function isWithin(beg,here,end)
{ // Simple utility
return ( beg <= here && here < end );
}
function convertToBases(pxX)
{ // Simple utility to convert pix to bases
var offset = (pxX - chr.left)/chr.width;
if (chr.reverse)
offset = 1 - offset;
return Math.round(offset * chr.size);
}
function convertFromBases(bases)
{ // Simple utility to convert bases to pix
var offset = bases/chr.size;
if (chr.reverse)
offset = 1 - offset;
return Math.round(offset * chr.width) + chr.left;
}
function findDimensions()
{ // Called at init: determine the dimensions of chrom from 'cytoband' map items
var lastX = -1;
$('area.cytoBand').each(function(ix) {
var loc = this.coords.split(",");
if (loc.length === 4) {
var myLeft = parseInt(loc[0]);
var myRight = parseInt(loc[2]);
if (chr.top === -1) {
chr.left = myLeft;
chr.right = myRight;
chr.top = parseInt(loc[1]);
chr.bottom = parseInt(loc[3]);
} else {
if (chr.left > myLeft)
chr.left = myLeft;
if (chr.right < parseInt(loc[2]))
chr.right = parseInt(loc[2]);
}
var range = this.title.substr(this.title.lastIndexOf(':')+1);
var pos = range.split('-');
if (pos.length === 2) {
if (chr.name.length === 0) {
chr.beg = parseInt(pos[0]);
//chr.end = parseInt(pos[1]);
chr.name = this.title.substring(this.title.lastIndexOf(' ')+1,
this.title.lastIndexOf(':'));
} else {
if (chr.beg > parseInt(pos[0]))
chr.beg = parseInt(pos[0]);
}
if (chr.end < parseInt(pos[1])) {
chr.end = parseInt(pos[1]);
if (lastX === -1)
lastX = myRight;
else if (lastX > myRight)
chr.reverse = true; // end is advancing, but X is not, so reverse
} else if (lastX !== -1 && lastX < myRight)
chr.reverse = true; // end is not advancing, but X is, so reverse
}
$(this).css( 'cursor', 'text');
$(this).attr("href","");
}
});
chr.size = (chr.end - chr.beg );
chr.width = (chr.right - chr.left);
}
function findCytoBand(pxDown,pxUp)
{ // Called when mouseup and ctrl: Find the bounding cytoband dimensions (in pix and bases)
var cyto = { left: -1, right: -1, beg: -1, end: -1 };
$('area.cytoBand').each(function(ix) {
var loc = this.coords.split(",");
if (loc.length === 4) {
var myLeft = parseInt(loc[0]);
var myRight = parseInt(loc[2]);
var range;
var pos;
if (cyto.left === -1 || cyto.left > myLeft) {
if ( isWithin(myLeft,pxDown,myRight) || isWithin(myLeft,pxUp,myRight) ) {
cyto.left = myLeft;
range = this.title.substr(this.title.lastIndexOf(':')+1);
pos = range.split('-');
if (pos.length === 2) {
cyto.beg = (chr.reverse ? parseInt(pos[1]) : parseInt(pos[0]));
}
}
}
if (cyto.right === -1 || cyto.right < myRight) {
if ( isWithin(myLeft,pxDown,myRight) || isWithin(myLeft,pxUp,myRight) ) {
cyto.right = myRight;
range = this.title.substr(this.title.lastIndexOf(':')+1);
pos = range.split('-');
if (pos.length === 2) {
cyto.end = (chr.reverse ? parseInt(pos[0]) : parseInt(pos[1]));
}
}
}
}
});
return cyto;
}
function rangeNormalizeToChrom(selection,chrom)
{ // Called before presenting or using base range: make sure chrom selection
// is within chrom range
if (selection.end < selection.beg) {
var tmp = selection.end;
selection.end = selection.beg;
selection.beg = tmp;
}
selection.width = (selection.end - selection.beg);
selection.beg += 1;
if (selection.beg < chrom.beg) {
selection.beg = chrom.beg;
selection.end = chrom.beg + selection.width;
}
if (selection.end > chrom.end) {
selection.end = chrom.end;
selection.beg = chrom.end - selection.width;
if (selection.beg < chrom.beg) { // spans whole chrom
selection.width = (selection.end - chrom.beg);
selection.beg = chrom.beg + 1;
}
}
return selection;
}
function hiliteShow(down,cur)
{ // Called on mousemove, mouseup: set drag hilite dimensions
var topY = img.top;
var high = img.height;
var begX = -1;
var wide = -1;
if (cur < down) {
begX = cur + img.left;
wide = (down - cur);
} else {
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)
if (hilite === null) { // setup only once
hilite = jQuery("");
$(hilite).css({ backgroundColor: 'green', opacity: 0.4, borderStyle: 'solid',
borderWidth: '1px', bordercolor: '#0000FF' });
$(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 (theClient.isIePre11()) {
img.height = $(chrImg).outerHeight();
img.width = $(chrImg).outerWidth();
} else {
img.height = $(chrImg).height();
img.width = $(chrImg).width();
}
return img;
}
});
};
///////////////////////////
//// Drag Reorder Code ////
///////////////////////////
var dragReorder = {
setOrder: function (table)
{ // Sets the 'order' value for the image table after a drag reorder
var varsToUpdate = {};
$("tr.imgOrd").each(function (i) {
if ($(this).attr('abbr') !== $(this).attr('rowIndex').toString()) {
$(this).attr('abbr',$(this).attr('rowIndex').toString());
var name = this.id.substring('tr_'.length) + '_imgOrd';
varsToUpdate[name] = $(this).attr('abbr');
}
});
if (objNotEmpty(varsToUpdate)) {
cart.setVarsObj(varsToUpdate);
imageV2.markAsDirtyPage();
}
},
sort: function (table)
{ // Sets the table row order to match the order of the abbr attribute.
// This is needed for back-button, and for visBox changes combined with refresh.
var tbody = $(table).find('tbody')[0];
if (!tbody)
tbody = table;
// Do we need to sort?
var trs = tbody.rows;
var needToSort = false;
$(trs).each(function(ix) {
if ($(this).attr('abbr') !== $(this).attr('rowIndex').toString()) {
needToSort = true;
return false; // break for each() loops
}
});
if (!needToSort)
return false;
// Create array of tr holders to sort
var ary = [];
$(trs).each(function(ix) { // using sortTable found in utils.js
ary.push(new sortTable.field(parseInt($(this).attr('abbr')),false,this));
});
// Sort the array
ary.sort(sortTable.fieldCmp);
// most efficient reload of sorted rows I have found
var sortedRows = jQuery.map(ary, function(ary, i) { return ary.row; });
$(tbody).append( sortedRows ); // removes tr from current position and adds to end.
return true;
},
showCenterLabel: function (tr, show)
{ // Will show or hide centerlabel as requested
// adjust button, sideLabel height, sideLabelOffset and centerlabel display
if (!$(tr).hasClass('clOpt'))
return;
var center = normed($(tr).find(".sliceDiv.cntrLab"));
if (!center)
return;
var seen = ($(center).css('display') !== 'none');
if (show === seen)
return;
var centerHeight = $(center).height();
var btn = normed($(tr).find("p.btn"));
var side = normed($(tr).find(".sliceDiv.sideLab"));
if (btn && side) {
var sideImg = normed($(side).find("img"));
if (sideImg) {
var top = parseInt($(sideImg).css('top'));
if (show) {
$(btn).css('height',$(btn).height() + centerHeight);
$(side).css('height',$(side).height() + centerHeight);
top += centerHeight; // top is a negative number
$(sideImg).css( {'top': top.toString() + "px" });
$( center ).show();
} else if (!show) {
$(btn).css('height',$(btn).height() - centerHeight);
$(side).css('height',$(side).height() - centerHeight);
top -= centerHeight; // top is a negative number
$(sideImg).css( {'top': top.toString() + "px" });
$( center ).hide();
}
}
}
},
getContiguousRowSet: function (row)
{ // Returns the set of rows that are of the same class and contiguous
if (!row)
return null;
var btn = $( row ).find("p.btn");
if (btn.length === 0)
return null;
var classList = $( btn ).attr("class").split(" ");
var matchClass = classList[0];
var table = $(row).parents('table#imgTbl')[0];
var rows = $(table).find('tr');
// Find start index
var startIndex = $(row).attr('rowIndex');
var endIndex = startIndex;
for (var ix=startIndex-1; ix >= 0; ix--) {
btn = $( rows[ix] ).find("p.btn");
if (btn.length === 0)
break;
classList = $( btn ).attr("class").split(" ");
if (classList[0] !== matchClass)
break;
startIndex = ix;
}
// Find end index
for (var rIx=endIndex; rIx 0) {
if ( ! imageV2.lastTrack || imageV2.lastTrack.id !== id)
imageV2.lastTrack = rightClick.makeMapItem(id);
// currentMapItem gets set by mapItemMapOver. This is just backup
}
},
mapItemMouseOver: function ()
{
// Record data for current map area item
var id = this.id;
if (!id || id.length === 0) {
id = '';
var tr = $( this ).parents('tr.imgOrd');
if ( $(tr).length === 1 ) {
var a = /tr_(.*)/.exec($(tr).attr('id')); // voodoo
if (a && a[1]) {
id = a[1];
}
}
}
if (id.length > 0) {
rightClick.currentMapItem = rightClick.makeMapItem(id);
if (rightClick.currentMapItem) {
rightClick.currentMapItem.href = this.href;
rightClick.currentMapItem.title = this.title;
}
}
},
mapItemMouseOut: function ()
{
imageV2.lastTrack = rightClick.currentMapItem; // Just a backup
rightClick.currentMapItem = null;
},
init: function ()
{ // Make side buttons visible (must also be called when updating rows in the imgTbl).
var btns = $("p.btn");
if (btns.length > 0) {
dragReorder.zipButtons($('#imgTbl'));
$(btns).mouseenter( dragReorder.buttonMouseOver );
$(btns).mouseleave( dragReorder.buttonMouseOut );
$(btns).show();
}
var handle = $("td.dragHandle");
if (handle.length > 0) {
$(handle).mouseenter( dragReorder.dragHandleMouseOver );
$(handle).mouseleave( dragReorder.dragHandleMouseOut );
}
// setup mouse callbacks for the area tags
$(imageV2.imgTbl).find("tr").mouseover( dragReorder.trMouseOver );
$(".area").each( function(t) {
this.onmouseover = dragReorder.mapItemMouseOver;
this.onmouseout = dragReorder.mapItemMouseOut;
this.onclick = posting.mapClk;
});
}
};
//////////////////////////
//// Drag Scroll code ////
//////////////////////////
jQuery.fn.panImages = function(){
// globals across all panImages
genomePos.original = genomePos.getOriginalPos(); // redundant but makes certain original is set.
var leftLimit = hgTracks.imgBoxLeftLabel * -1;
var rightLimit = (hgTracks.imgBoxPortalWidth - hgTracks.imgBoxWidth + leftLimit);
var only1xScrolling = ((hgTracks.imgBoxWidth - hgTracks.imgBoxPortalWidth) === 0);
var prevX = (hgTracks.imgBoxPortalOffsetX + hgTracks.imgBoxLeftLabel) * -1;
var portalWidth = 0;
var portalAbsoluteX = 0;
var savedPosition;
var highlightArea = null; // Used to ensure dragSelect highlight will scroll.
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 || !pic) {
throw "Not a div with child image! 'panImages' can only be used with divs contain images.";
}
// globals across all panImages
portalWidth = $(pan).width();
portalAbsoluteX = $(pan).offset().left;
// globals to one panImage
var newX = 0;
var mouseDownX = 0;
var mouseIsDown = false;
var beyondImage = false;
var atEdge = false;
initialize();
function initialize(){
$(pan).parents('td.tdData').mousemove(function(e) {
if (e.shiftKey)
$(this).css('cursor',"crosshair"); // shift-dragZoom
else if ( theClient.isIePre11() ) // IE will override map item cursors if this gets set
$(this).css('cursor',""); // normal pointer when not over clickable item
});
panAdjustHeight(prevX);
pan.mousedown(function(e){
if (e.which > 1 || e.button > 1 || e.shiftKey)
return true;
if (mouseIsDown === false) {
if (rightClick.menu) {
rightClick.menu.hide();
}
mouseIsDown = true;
mouseDownX = e.clientX;
highlightArea = $('#highlightItem')[0];
atEdge = (!beyondImage && (prevX >= leftLimit || prevX <= rightLimit));
$(document).bind('mousemove',panner);
$(document).bind( 'mouseup', panMouseUp); // Will exec only once
return false;
}
});
}
function panner(e) {
//if (!e) e = window.event;
if ( mouseIsDown ) {
var relativeX = (e.clientX - mouseDownX);
if (relativeX !== 0) {
if (posting.mapClicksAllowed()) {
// need to throw up a z-index div. Wait mask?
savedPosition = genomePos.get();
dragMaskShow();
posting.blockMapClicks();
}
var decelerator = 1;
//var wingSize = 1000; // 0 stops the scroll at the edges.
// Remeber that offsetX (prevX) is negative
newX = prevX + relativeX;
if ( newX >= leftLimit ) { // scrolled all the way to the left
if (atEdge) { // Do not drag straight off edge. Force second drag
beyondImage = true;
newX = leftLimit + (newX - leftLimit)/decelerator;// slower
//if (newX >= leftLimit + wingSize) // Don't go too far over the edge!
// newX = leftLimit + wingSize;
} else
newX = leftLimit;
} else if ( newX < rightLimit ) { // scrolled all the way to the right
if (atEdge) { // Do not drag straight off edge. Force second drag
beyondImage = true;
newX = rightLimit - (rightLimit - newX)/decelerator;// slower
//if (newX < rightLimit - wingSize) // Don't go too far over the edge!
// newX = rightLimit - wingSize;
} else
newX = rightLimit;
} else if (newX >= rightLimit && newX < leftLimit)
beyondImage = false; // could have scrolled back without mouse up
newX = panUpdatePosition(newX,true);
var nowPos = newX.toString() + "px";
$(".panImg").css( {'left': nowPos });
$('.tdData').css( {'backgroundPosition': nowPos } );
scrollHighlight(relativeX);
if (!only1xScrolling)
panAdjustHeight(newX); // Will dynamically resize image while scrolling.
}
}
}
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;
// timeout incase the dragSelect.selectEnd was over a map item. select takes precedence.
setTimeout(posting.allowMapClicks,50);
// Outside image? Then abandon.
var curY = e.pageY;
var imgTbl = $('#imgTbl');
var north = $(imgTbl).offset().top;
var south = north + $(imgTbl).height();
if (curY < north || curY > south) {
atEdge = false;
beyondImage = false;
if (savedPosition)
genomePos.set(savedPosition,null);
var oldPos = prevX.toString() + "px";
$(".panImg").css( {'left': oldPos });
$('.tdData').css( {'backgroundPosition': oldPos } );
if (highlightArea)
imageV2.highlightRegion();
return true;
}
// Do we need to fetch anything?
if (beyondImage) {
if (imageV2.inPlaceUpdate) {
var pos = parsePosition(genomePos.get());
imageV2.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); // Will resize image AFTER scrolling.
// 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 closedPortalStart = hgTracks.imgBoxPortalStart + 1; // Correction for half open
var portalWidthBases = hgTracks.imgBoxPortalEnd - closedPortalStart;
var portalScrolledX = hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel + newOffsetX;
var recalculate = false;
var newPortalStart = 0;
if (hgTracks.revCmplDisp)
newPortalStart = closedPortalStart + // As offset goes down, so do bases seen.
Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel);
else
newPortalStart = closedPortalStart - // As offset goes down, bases seen goes up!
Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel);
if (newPortalStart < hgTracks.chromStart && bounded) { // Stay within bounds
newPortalStart = hgTracks.chromStart;
recalculate = true;
}
var newPortalEnd = newPortalStart + portalWidthBases;
if (newPortalEnd > hgTracks.chromEnd && bounded) {
newPortalEnd = hgTracks.chromEnd;
newPortalStart = newPortalEnd - portalWidthBases;
recalculate = true;
}
if (newPortalStart > 0) {
var newPos = hgTracks.chromName + ":" +
commify(newPortalStart) + "-" + commify(newPortalEnd);
genomePos.set(newPos, 0); // 0 means no need to change the size
}
if (recalculate && hgTracks.imgBoxBasesPerPixel > 0) {
// Need to recalculate X for bounding drag
portalScrolledX = (closedPortalStart - newPortalStart) / hgTracks.imgBoxBasesPerPixel;
newOffsetX = portalScrolledX - (hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel);
}
return newOffsetX;
}
function mapTopAndBottom(mapName,east,west)
{
// Find the top and bottom px given left and right boundaries
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 < west && aright >= east) {
var atop = parseInt(loc[1]);
var abottom = parseInt(loc[3]);
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 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 east = newOffsetX * -1;
var west = east + portalWidth;
$(".panImg").each(function(t) {
var mapid = this.id.replace('img_','map_');
var hDiv = $(this).parent();
var north = parseInt($(this).css("top")) * -1;
var south = north + $(hDiv).height();
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( 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();
north += titlePx;
}
var side = $("#img_side_"+imgId[2]);
if (side.length > 0) {
$(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( mapPortal.bottom - mapPortal.top + titlePx);
} else {
btn = $("#img_btn_"+imgId[2]);
if (btn.length > 0) {
$(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 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 = normed($('div#dragMask'));
if (!dragMask) {
$("body").prepend("");
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' });
}
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 = normed($('div#dragMask'));
if (dragMask) {
$(dragMask).height( $(imgTbl).height() );
}
}
function dragMaskClear() { // Clears the dragMask
$('body').css('cursor','auto');
var dragMask = normed($('#dragMask'));
if (dragMask)
$(dragMask).hide();
}
function scrollHighlight(relativeX)
// Scrolls the highlight region if one exists
{
if (highlightArea) {
// Best to have a left and right, then min/max the edges, then set width
var hiOffset = $(highlightArea).offset();
var hiDefinedLeft = $(highlightArea).data('leftPixels');
var hiDefinedWidth = $(highlightArea).data('widthPixels');
hiOffset.left = Math.max(hiDefinedLeft + relativeX,
portalAbsoluteX);
var right = Math.min(hiDefinedLeft + hiDefinedWidth + relativeX,
portalAbsoluteX + portalWidth);
var newWidth = Math.max(right - hiOffset.left,0);
if (hiDefinedWidth !== newWidth)
$(highlightArea).width(newWidth);
$(highlightArea).offset(hiOffset);
}
}
};
///////////////////////////////////////
//// rightClick (aka context menu) ////
///////////////////////////////////////
var rightClick = {
menu: null,
selectedMenuItem: null, // currently choosen context menu item (via context menu).
floatingMenuItem: null,
currentMapItem: null,
supportZoomCodon: false, // turns on experimental feature (currently only in larry's tree).
makeMapItem: function (id)
{ // Create a dummy mapItem on the fly
// (for objects that don't have corresponding entry in the map).
if (id && id.length > 0 && hgTracks.trackDb) {
var title;
var rec = hgTracks.trackDb[id];
if (rec) {
title = rec.shortLabel;
} else {
title = id;
}
return {id: id, title: "configure " + title};
} else {
return null;
}
},
findMapItem: function (e)
{ // Find mapItem for given event; returns item object or null if none found.
if (rightClick.currentMapItem) {
return rightClick.currentMapItem;
}
// rightClick for non-map items that can be resolved to their parent tr and
// then trackName (e.g. items in gray bar)
var tr = $( e.target ).parents('tr.imgOrd');
if ($(tr).length === 1) {
var a = /tr_(.*)/.exec($(tr).attr('id')); // voodoo
if (a && a[1]) {
var id = a[1];
return rightClick.makeMapItem(id);
}
}
return null;
},
windowOpenFailedMsg: function ()
{
warn("Your web browser prevented us from opening a new window.\n\n" +
"Please change your browser settings to allow pop-up windows from " +
document.domain + ".");
},
handleZoomCodon: function (response, status)
{
var json = JSON.parse(response);
if (json.pos) {
genomePos.set(json.pos, 3);
if (document.TrackForm)
document.TrackForm.submit();
else
document.TrackHeaderForm.submit();
} else {
alert(json.error);
}
},
handleViewImg: function (response, status)
{ // handles view image response, which must get new image without imageV2 gimmickery
jQuery('body').css('cursor', '');
var str = "]*SRC='([^']+)'";
var reg = new RegExp(str);
var a = reg.exec(response);
if (a && a[1]) {
if ( ! window.open(a[1]) ) {
rightClick.windowOpenFailedMsg();
}
return;
}
warn("Couldn't parse out img src");
},
myPrompt: function (msg, callback)
{ // replacement for prompt; avoids misleading/confusing security warnings which are caused
// by prompt in IE 7+. Callback is called if user presses "OK".
$("body").append("
");
$("#myPrompt").dialog({
modal: true,
closeOnEscape: true,
buttons: { "OK": function() {
var myPromptText = $("#myPromptText").val();
$(this).dialog("close");
callback(myPromptText);
}
}
});
},
hit: function (menuItemClicked, menuObject, cmd, args)
{
setTimeout( function() {
rightClick.hitFinish(menuItemClicked, menuObject, cmd, args);
}, 1);
},
hitFinish: function (menuItemClicked, menuObject, cmd, args)
{ // dispatcher for context menu hits
var id = rightClick.selectedMenuItem.id;
var url = null; // TODO: Break this giant routine with shared vars into some sub-functions
var href = null;
var rec = null;
var row = null;
var rows = null;
var selectUpdated = null;
if (menuObject.shown) {
// warn("Spinning: menu is still shown");
setTimeout(function() { rightClick.hitFinish(menuItemClicked, menuObject, cmd); }, 10);
return;
}
if (cmd === 'selectWholeGene' || cmd === 'getDna' || cmd === 'highlightItem') {
// bring whole gene into view or redirect to DNA screen.
href = rightClick.selectedMenuItem.href;
var chromStart, chromEnd;
var a = /hgg_chrom=(\w+)&/.exec(href);
// Many links leave out the chrom (b/c it's in the server side cart as "c")
var chrom = hgTracks.chromName;
if (a) {
if (a && a[1])
chrom = a[1];
a = /hgg_start=(\d+)/.exec(href);
if (a && a[1])
chromStart = parseInt(a[1]) + 1;
a = /hgg_end=(\d+)/.exec(href);
if (a && a[1])
chromEnd = parseInt(a[1]);
} else {
// a = /hgc.*\W+c=(\w+)/.exec(href);
a = /hgc.*\W+c=(\w+)/.exec(href);
if (a && a[1])
chrom = a[1];
a = /o=(\d+)/.exec(href);
if (a && a[1])
chromStart = parseInt(a[1]) + 1;
a = /t=(\d+)/.exec(href);
if (a && a[1])
chromEnd = parseInt(a[1]);
}
if (!chrom || chrom.length === 0 || !chromStart || !chromEnd) {// 1-based chromStart
warn("couldn't parse out genomic coordinates");
} else {
if (cmd === 'getDna') {
// NOTE: this should be shared with URL generation for getDna blue bar menu
url = "../cgi-bin/hgc?g=getDna&i=mixed&c=" + chrom;
url += "&l=" + (chromStart - 1) + "&r=" + chromEnd;
url += "&db=" + getDb() + "&hgsid=" + getHgsid();
if ( ! window.open(url) ) {
rightClick.windowOpenFailedMsg();
}
} else if (cmd === 'highlightItem') {
dragSelect.highlightThisRegion(parseInt(chromStart), parseInt(chromEnd));
} else {
var newPosition = genomePos.setByCoordinates(chrom, chromStart, chromEnd);
var reg = new RegExp("hgg_gene=([^&]+)");
var b = reg.exec(href);
var name;
// pull item name out of the url so we can set hgFind.matches (redmine 3062)
if (b && b[1]) {
name = b[1];
} else {
reg = new RegExp("[&?]i=([^&]+)");
b = reg.exec(href);
if (b && b[1]) {
name = b[1];
}
}
if (imageV2.inPlaceUpdate) {
// XXXX This attempt to "update whole track image in place" didn't work
// for a variety of reasons (e.g. safari doesn't parse map when we
// update on the client side), so this is currently dead code.
// However, this now works in all other browsers, so we may turn this
// on for non-safari browsers (see redmine #4667).
jQuery('body').css('cursor', '');
var data = "hgt.trackImgOnly=1&hgt.ideogramToo=1&position=" +
newPosition + "&hgsid=" + getHgsid();
if (name)
data += "&hgFind.matches=" + name;
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: cart.addUpdatesToUrl(data),
dataType: "html",
trueSuccess: imageV2.updateImgAndMap,
success: catchErrorOrDispatch,
error: errorHandler,
cmd: cmd,
loadingId: showLoadingImage("imgTbl"),
cache: false
});
} else {
// do a full page refresh to update hgTracks image
jQuery('body').css('cursor', 'wait');
var ele;
if (document.TrackForm)
ele = document.TrackForm;
else
ele = document.TrackHeaderForm;
if (name)
cart.addVarsToQueue(['hgFind.matches'], [name]);
ele.submit();
}
}
}
} else if (cmd === 'zoomCodon' || cmd === 'zoomExon') {
var num, ajaxCmd, msg;
if (cmd === 'zoomCodon') {
msg = "Please enter the codon number to jump to:";
ajaxCmd = 'codonToPos';
} else {
msg = "Please enter the exon number to jump to:";
ajaxCmd = 'exonToPos';
}
rightClick.myPrompt(msg, function(results) {
$.ajax({
type: "GET",
url: "../cgi-bin/hgApi",
data: cart.varsToUrlData({ 'db': getDb(), 'cmd': ajaxCmd, 'num': results,
'table': args.table, 'name': args.name }),
trueSuccess: rightClick.handleZoomCodon,
success: catchErrorOrDispatch,
error: errorHandler,
cache: true
});
});
} else if (cmd === 'hgTrackUi_popup') {
// Launches the popup but shields the ajax with a waitOnFunction
popUp.hgTrackUi( rightClick.selectedMenuItem.id, false );
} else if (cmd === 'hgTrackUi_follow') {
url = "hgTrackUi?hgsid=" + getHgsid() + "&g=";
rec = hgTracks.trackDb[id];
if (tdbHasParent(rec) && tdbIsLeaf(rec))
url += rec.parentTrack;
else {
// The button already has the ref
var link = normed($( 'td#td_btn_'+ rightClick.selectedMenuItem.id ).children('a'));
if (link)
url = $(link).attr('href');
else
url += rightClick.selectedMenuItem.id;
}
location.assign(url);
} else if (cmd === 'viewImg') {
// Fetch a new copy of track img and show it to the user in another window. This code
// assume we have updated remote cart with all relevant chages (e.g. drag-reorder).
jQuery('body').css('cursor', 'wait');
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: cart.varsToUrlData({ 'hgt.imageV1': '1','hgt.trackImgOnly': '1',
'hgsid': getHgsid() }),
dataType: "html",
trueSuccess: rightClick.handleViewImg,
success: catchErrorOrDispatch,
error: errorHandler,
cmd: cmd,
cache: false
});
} else if (cmd === 'openLink' || cmd === 'followLink') {
href = rightClick.selectedMenuItem.href;
var vars = new Array("c", "l", "r", "db");
var valNames = new Array("chromName", "winStart", "winEnd");
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).
location.assign(href);
} else {
// Remove hgsid to force a new session (see redmine ticket 1333).
href = removeHgsid(href);
if ( ! window.open(href) ) {
rightClick.windowOpenFailedMsg();
}
}
} else if (cmd === 'float') {
if (rightClick.floatingMenuItem && rightClick.floatingMenuItem === id) {
$.floatMgr.FOArray = [];
rightClick.floatingMenuItem = null;
} else {
if (rightClick.floatingMenuItem) {
// This doesn't work.
$('#img_data_' + rightClick.floatingMenuItem).parent().restartFloat();
// This does work
$.floatMgr.FOArray = [];
}
rightClick.floatingMenuItem = id;
rightClick.reloadFloatingItem();
imageV2.requestImgUpdate(id, "hgt.transparentImage=0", "");
}
} else if (cmd === 'hideSet') {
row = $( 'tr#tr_' + id );
rows = dragReorder.getContiguousRowSet(row);
if (rows && rows.length > 0) {
var varsToUpdate = {};
// from bottom up, just in case remove screws with us
for (var ix=rows.length - 1; ix >= 0; ix--) {
var rowId = $(rows[ix]).attr('id').substring('tr_'.length);
// Remove subtrack level vis and explicitly uncheck.
varsToUpdate[rowId] = '[]';
varsToUpdate[rowId+'_sel'] = 0;
$(rows[ix]).remove();
}
if (objNotEmpty(varsToUpdate)) {
cart.setVarsObj(varsToUpdate);
}
imageV2.afterImgChange(true);
}
} else if (cmd === 'hideComposite') {
rec = hgTracks.trackDb[id];
if (tdbIsSubtrack(rec)) {
row = $( 'tr#tr_' + id );
rows = dragReorder.getCompositeSet(row);
// from bottom up, just in case remove screws with us
if (rows && rows.length > 0) {
for (var rIx=rows.length - 1; rIx >= 0; rIx--) {
$(rows[rIx]).remove();
}
selectUpdated = vis.update(rec.parentTrack, 'hide');
cart.setVars( [rec.parentTrack], ['hide']);
imageV2.afterImgChange(true);
}
}
} else if (cmd === 'jumpToHighlight') { // If highlight exists for this assembly, jump to it
if (hgTracks.highlight) {
var newPos = parsePositionWithDb(hgTracks.highlight);
if (newPos && newPos.db === getDb()) {
if ( $('#highlightItem').length === 0) { // not visible? jump to it
var curPos = parsePosition(genomePos.get());
var diff = ((curPos.end - curPos.start) - (newPos.end - newPos.start));
if (diff > 0) { // new position is smaller then current, then center it
newPos.start = Math.max( Math.floor(newPos.start - (diff/2) ), 0 );
newPos.end = newPos.start + (curPos.end - curPos.start);
}
}
if (imageV2.inPlaceUpdate) {
var params = "position=" + newPos.chrom+':'+newPos.start+'-'+newPos.end;
imageV2.navigateInPlace(params, null, true);
} else {
genomePos.setByCoordinates(newPos.chrom, newPos.start, newPos.end);
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
}
}
}
} else if (cmd === 'removeHighlight') {
hgTracks.highlight = null;
cart.setVarsObj({ 'highlight': '[]' });
imageV2.highlightRegion();
} else { // if ( cmd in 'hide','dense','squish','pack','full','show' )
// Change visibility settings:
//
// First change the select on our form:
rec = hgTracks.trackDb[id];
selectUpdated = vis.update(id, cmd);
// Now change the track image
if (imageV2.enabled && cmd === 'hide') {
// Hide local display of this track and update server side cart.
// Subtracks controlled by 2 settings so del vis and set sel=0.
if (tdbIsSubtrack(rec)) {
// Remove subtrack level vis and explicitly uncheck.
cart.setVars( [ id, id+"_sel" ], [ '[]', 0 ] );
} else if (tdbIsFolderContent(rec)) {
// supertrack children need to have _sel set to trigger superttrack reshaping
cart.setVars( [ id, id+"_sel" ], [ 'hide', 0 ] );
} else {
cart.setVars([id], ['hide']); // Others, just set vis hide.
}
$(document.getElementById('tr_' + id)).remove();
imageV2.afterImgChange(true);
} else if (!imageV2.mapIsUpdateable) {
jQuery('body').css('cursor', 'wait');
if (selectUpdated) {
// assert(document.TrackForm);
document.TrackForm.submit();
} else {
// Add vis update to queue then submit
cart.addVarsToQueue([id], [cmd]);
document.TrackHeaderForm.submit();
}
} else {
imageV2.requestImgUpdate(id, id + "=" + cmd, "", cmd);
}
}
},
makeHitCallback: function (title)
{ // stub to avoid problem with a function closure w/n a loop
return function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, title); return true;
};
},
reloadFloatingItem: function ()
{ // currently dead (experimental code)
if (rightClick.floatingMenuItem) {
$('#img_data_' + rightClick.floatingMenuItem).parent().makeFloat(
{x:"current",y:"current", speed: 'fast', alwaysVisible: true, alwaysTop: true});
}
},
makeImgTag: function (img)
{ // Return img tag with explicit dimensions for img (dimensions are currently hardwired).
// This fixes the "weird shadow problem when first loading the right-click menu"
// seen in FireFox 3.X, which occurred b/c FF doesn't actually fetch the image until
// the menu is being shown.
return "";
},
load: function (img)
{
rightClick.menu = img.contextMenu(function() {
popUp.cleanup(); // Popup box is not getting closed properly so must do it here
if ( ! rightClick.selectedMenuItem ) // This is literally an edge case so ignore
return;
var o; // TODO: Break this giant routine with shared vars into some sub-functions
var str;
var rec = null;
var menu = [];
var selectedImg = rightClick.makeImgTag("greenChecksm.png");
var blankImg = rightClick.makeImgTag("invisible16.png");
var done = false;
if (rightClick.selectedMenuItem && rightClick.selectedMenuItem.id) {
var href = rightClick.selectedMenuItem.href;
var isHgc, isGene;
if (href) {
isGene = href.match("hgGene");
isHgc = href.match("hgc");
}
var id = rightClick.selectedMenuItem.id;
rec = hgTracks.trackDb[id];
var offerHideSubset = false;
var offerHideComposite = false;
var offerSingles = true;
var row = $( 'tr#tr_' + id );
if (row) {
var btn = $(row).find('p.btnBlue'); // btnBlue means cursor over left button
if (btn.length === 1) {
var compositeSet = dragReorder.getCompositeSet(row);
if (compositeSet && compositeSet.length > 0) { // There is composite set
offerHideComposite = true;
$( compositeSet ).find('p.btn').addClass('blueButtons');// blue persists
var subSet = dragReorder.getContiguousRowSet(row);
if (subSet && subSet.length > 1) {
offerSingles = false;
if (subSet.length < compositeSet.length) {
offerHideSubset = true;
$( subSet ).addClass("greenRows"); // green persists
}
}
}
}
}
// First option is hide sets
if (offerHideComposite) {
if (offerHideSubset) {
o = {};
o[blankImg + " hide track subset (green)"] = {
onclick: rightClick.makeHitCallback('hideSet')};
menu.push(o);
}
o = {};
str = blankImg + " hide track set";
if (offerHideSubset)
str += " (blue)";
o[str] = {onclick: rightClick.makeHitCallback('hideComposite')};
menu.push(o);
}
// Second set of options: visibility for single track
if (offerSingles) {
if (offerHideComposite)
menu.push($.contextMenu.separator);
// XXXX what if select is not available (b/c trackControlsOnMain is off)?
// Move functionality to a hidden variable?
var select = $("select[name=" + escapeJQuerySelectorChars(id) + "]");
if (select.length > 1)
// Not really needed if $('#hgTrackUiDialog').html(""); has worked
select = [ $(select)[0] ];
var cur = $(select).val();
if (cur) {
$(select).children().each(function(index, o) {
var title = $(this).val();
str = blankImg + " " + title;
if (title === cur)
str = selectedImg + " " + title;
o = {};
o[str] = {onclick: function (menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, title);
return true;}};
menu.push(o);
});
done = true;
} else {
if (rec) {
// XXXX check current state from a hidden variable.
var visStrings = new Array("hide","dense","squish","pack","full");
for (var i in visStrings) {
// use maxVisibility and change hgTracks so it can hide subtracks
o = {};
str = blankImg + " " + visStrings[i];
if (rec.canPack
|| (visStrings[i] !== "pack" && visStrings[i] !== "squish")) {
if (rec.localVisibility) {
if (visStrings[i] === rec.localVisibility) {
str = selectedImg + " " + visStrings[i];
}
} else if (visStrings[i] === vis.enumOrder[rec.visibility]) {
str = selectedImg + " " + visStrings[i];
}
o[str] = { onclick:
rightClick.makeHitCallback(visStrings[i])
};
menu.push(o);
}
}
done = true;
}
}
}
if (done) {
o = {};
var any = false;
var title = rightClick.selectedMenuItem.title || "feature";
var maxLength = 60;
if (title.length > maxLength) {
title = title.substring(0, maxLength) + "...";
}
if (isGene || isHgc || id === "wikiTrack") {
// Add "Open details..." item
var displayItemFunctions = false;
if (rec) {
if (rec.type.indexOf("wig") === 0
|| rec.type.indexOf("bigWig") === 0
|| id === "wikiTrack") {
displayItemFunctions = false;
} else if (rec.type.indexOf("expRatio") === 0) {
displayItemFunctions = title !== "zoomInMore";
} else {
displayItemFunctions = true;
}
}
if (displayItemFunctions) {
o[rightClick.makeImgTag("magnify.png") + " Zoom to " + title] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"selectWholeGene"); return true;
}
};
o[rightClick.makeImgTag("highlight.png") + " Highlight " + title] =
{ onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"highlightItem");
return true;
}
};
if (rightClick.supportZoomCodon && rec.type.indexOf("genePred") !== -1) {
// http://hgwdev-larrym.cse.ucsc.edu/cgi-bin/hgGene?hgg_gene=uc003tqk.2&hgg_prot=P00533&hgg_chrom=chr7&hgg_start=55086724&hgg_end=55275030&hgg_type=knownGene&db=hg19&c=chr7
var name, table;
var reg = new RegExp("hgg_gene=([^&]+)");
var a = reg.exec(href);
if (a && a[1]) {
name = a[1];
reg = new RegExp("hgg_type=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
table = a[1];
}
} else {
// http://hgwdev-larrym.cse.ucsc.edu/cgi-bin/hgc?o=55086724&t=55275031&g=refGene&i=NM_005228&c=chr7
// http://hgwdev-larrym.cse.ucsc.edu/cgi-bin/hgc?o=55086713&t=55270769&g=wgEncodeGencodeManualV4&i=ENST00000455089&c=chr7
reg = new RegExp("i=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
name = a[1];
reg = new RegExp("g=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
table = a[1];
}
}
}
if (name && table) {
o[rightClick.makeImgTag("magnify.png")+" Zoom to codon"] =
{ onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"zoomCodon",
{name: name, table: table});
return true;}
};
o[rightClick.makeImgTag("magnify.png")+" Zoom to exon"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"zoomExon",
{name: name, table: table});
return true; }
};
}
}
o[rightClick.makeImgTag("dnaIcon.png")+" Get DNA for "+title] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "getDna");
return true; }
};
}
o[rightClick.makeImgTag("bookOut.png")+
" Open details page in new window..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "openLink");
return true; }
};
any = true;
}
if (href && href.length > 0) {
// Add "Show details..." item
if (title.indexOf("Click to alter ") === 0) {
// suppress the "Click to alter..." items
} else if (rightClick.selectedMenuItem.href.indexOf("cgi-bin/hgTracks")
!== -1) {
// suppress menu items for hgTracks links (e.g. Next/Prev map items).
} else {
var item;
if (title === "zoomInMore")
// avoid showing menu item that says
// "Show details for zoomInMore..." (redmine 2447)
item = rightClick.makeImgTag("book.png") + " Show details...";
else
item = rightClick.makeImgTag("book.png")+" Show details for "+
title + "...";
o[item] = {onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked,menuObject,"followLink");
return true; }
};
any = true;
}
}
if (any) {
menu.push($.contextMenu.separator);
menu.push(o);
}
}
}
if (rightClick.selectedMenuItem && rec) {
// Add cfg options at just shy of end...
o = {};
if (tdbIsLeaf(rec)) {
if (rec.configureBy !== 'none'
&& (!tdbIsCompositeSubtrack(rec) || rec.configureBy !== 'clickThrough')) {
// Note that subtracks never do clickThrough because
// parentTrack cfg is the desired clickThrough
o[rightClick.makeImgTag("wrench.png")+" Configure "+rec.shortLabel] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "hgTrackUi_popup");
return true; }
};
}
if (rec.parentTrack) {
o[rightClick.makeImgTag("folderWrench.png")+" Configure "+
rec.parentLabel + " track set..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked,menuObject,"hgTrackUi_follow");
return true; }
};
}
} else {
o[rightClick.makeImgTag("folderWrench.png")+" Configure "+rec.shortLabel +
" track set..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "hgTrackUi_follow");
return true; }
};
}
if (jQuery.floatMgr) {
o[(rightClick.selectedMenuItem.id === rightClick.floatingMenuItem ?
selectedImg : blankImg) + " float"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "float");
return true; }
};
}
menu.push($.contextMenu.separator);
menu.push(o);
}
menu.push($.contextMenu.separator);
if (hgTracks.highlight) {
if (hgTracks.highlight.search(getDb() + '.') === 0) {
var currentlySeen = ($('#highlightItem').length > 0);
o = {};
// Jumps to highlight when not currently seen in image
var text = (currentlySeen ? " Zoom" : " Jump") + " to highlighted region";
o[rightClick.makeImgTag("highlightZoom.png") + text] = {
onclick: rightClick.makeHitCallback('jumpToHighlight')
};
if ( currentlySeen ) { // Remove only when seen
o[rightClick.makeImgTag("highlightRemove.png") +
" Remove highlighting"] = {
onclick: rightClick.makeHitCallback('removeHighlight')
};
}
menu.push(o);
}
}
// Add view image at end
o = {};
o[rightClick.makeImgTag("eye.png") + " View image"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "viewImg");
return true; }
};
menu.push(o);
return menu;
},
{
beforeShow: function(e) {
// console.log(mapItems[rightClick.selectedMenuItem]);
rightClick.selectedMenuItem = rightClick.findMapItem(e);
// XXXX? posting.blockUseMap = true;
return true;
},
hideTransition:'hide', // hideCallback fails if these are not defined.
hideSpeed:10,
hideCallback: function() {
$('p.btn.blueButtons').removeClass('blueButtons');
$('tr.trDraggable.greenRows').removeClass('greenRows');
}
});
return;
}
};
//////////////////////////////////
//// popup (aka modal dialog) ////
//////////////////////////////////
var popUp = {
trackName: "",
trackDescriptionOnly: false,
saveAllVars: null,
cleanup: function ()
{ // Clean out the popup box on close
if ($('#hgTrackUiDialog').html().length > 0 ) {
// clear out html after close to prevent problems caused by duplicate html elements
$('#hgTrackUiDialog').html("");
popUp.trackName = ""; //set to defaults
popUp.trackDescriptionOnly = false;
popUp.saveAllVars = null;
}
},
_uiDialigRequest: function (trackName,descriptionOnly)
{ // popup cfg dialog
popUp.trackName = trackName;
var myLink = "../cgi-bin/hgTrackUi?g=" + trackName + "&hgsid=" + getHgsid() +
"&db=" + getDb();
popUp.trackDescriptionOnly = descriptionOnly;
if (popUp.trackDescriptionOnly)
myLink += "&descriptionOnly=1";
var rec = hgTracks.trackDb[trackName];
if (!descriptionOnly && rec && rec.configureBy) {
if (rec.configureBy === 'none')
return;
else if (rec.configureBy === 'clickThrough') {
jQuery('body').css('cursor', 'wait');
window.location = cart.addUpdatesToUrl(myLink);
return;
} // default falls through to configureBy popup
}
myLink += "&ajax=1";
$.ajax({
type: "GET",
url: cart.addUpdatesToUrl(myLink),
dataType: "html",
trueSuccess: popUp.uiDialog,
success: catchErrorOrDispatch,
error: errorHandler,
cmd: rightClick.selectedMenuItem,
cache: false
});
},
hgTrackUi: function (trackName,descriptionOnly)
{ // Launches the popup but shields the ajax with a waitOnFunction
waitOnFunction( popUp._uiDialigRequest, trackName, descriptionOnly );
},
uiDialogOk: function (popObj, trackName)
{ // When hgTrackUi Cfg popup closes with ok, then update cart and refresh parts of page
var rec = hgTracks.trackDb[trackName];
var subtrack = tdbIsSubtrack(rec) ? trackName : undefined; // subtrack vis rules differ
// For unknown reasons IE8 fails to find $('#pop'), occasionally
var allVars = getAllVars($('#hgTrackUiDialog'), subtrack );
var changedVars = varHashChanges(allVars,popUp.saveAllVars);
var newVis = changedVars[trackName];
// subtracks do not have "hide", thus '[]'
var hide = (newVis && (newVis === 'hide' || newVis === '[]'));
if ( ! normed($('#imgTbl')) ) { // On findTracks or config page
if (objNotEmpty(changedVars))
cart.setVarsObj(changedVars);
}
else { // On image page
if (hide) {
if (objNotEmpty(changedVars))
cart.setVarsObj(changedVars);
$(document.getElementById('tr_' + trackName)).remove();
imageV2.afterImgChange(true);
} else {
// Keep local state in sync if user changed visibility
if (newVis) {
vis.update(trackName, newVis);
}
if (objNotEmpty(changedVars)) {
var urlData = cart.varsToUrlData(changedVars);
if (imageV2.mapIsUpdateable) {
imageV2.requestImgUpdate(trackName,urlData,"");
} else {
window.location = "../cgi-bin/hgTracks?" + urlData + "&hgsid=" + getHgsid();
}
}
}
}
},
uiDialog: function (response, status)
{
// Take html from hgTrackUi and put it up as a modal dialog.
// make sure all links (e.g. help links) open up in a new window
response = response.replace(/"+ cleanHtml +"
");
// Strategy for poups with js:
// - jsFiles and CSS should not be included in html. Here they are shluped out.
// - The resulting files ought to be loadable dynamically (with getScript()),
// but this was not working nicely with the modal dialog
// Therefore include files must be included with hgTracks CGI !
// - embedded js should not be in the popup box.
// - Somethings should be in a popup.ready() function, and this is emulated below,
// as soon as the cleanHtml is added
// Since there are many possible popup cfg dialogs, the ready should be all inclusive.
if ( ! popUp.trackDescriptionOnly ) {
// If subtrack then vis rules differ
var subtrack = tdbIsSubtrack(hgTracks.trackDb[popUp.trackName]) ? popUp.trackName :"";
// Saves the original vars (and vals) that may get changed by the popup cfg.
popUp.saveAllVars = getAllVars( $('#hgTrackUiDialog'), subtrack );
// -- popup.ready() -- Here is the place to do things that might otherwise go
// into a $('#pop').ready() routine!
}
// Searching for some selblance of size suitability
var popMaxHeight = ($(window).height() - 40);
var popMaxWidth = ($(window).width() - 40);
var popWidth = 740;
if (popWidth > popMaxWidth)
popWidth = popMaxWidth;
$('#hgTrackUiDialog').dialog({
ajaxOptions: {
// This doesn't work
cache: true
},
resizable: true, // Let description scroll vertically
height: (popUp.trackDescriptionOnly ? popMaxHeight : 'auto'),
width: popWidth,
minHeight: 200,
minWidth: 700,
maxHeight: popMaxHeight,
maxWidth: popMaxWidth,
modal: true,
closeOnEscape: true,
autoOpen: false,
buttons: { "OK": function() {
if ( ! popUp.trackDescriptionOnly )
popUp.uiDialogOk($('#pop'), popUp.trackName);
$(this).dialog("close");
}},
// popup.ready() doesn't seem to work in open.
open: function () {
if ( ! popUp.trackDescriptionOnly ) {
$('#hgTrackUiDialog').find('.filterBy,.filterComp').each(
function(i) {
if ($(this).hasClass('filterComp'))
ddcl.setup(this);
else
ddcl.setup(this, 'noneIsAll');
}
);
}
},
close: function() {
popUp.cleanup();
}
});
// FIXME: Why are open and close no longer working!!!
if (popUp.trackDescriptionOnly) {
var myWidth = $(window).width() - 300;
if (myWidth > 900)
myWidth = 900;
$('#hgTrackUiDialog').dialog("option", "maxWidth", myWidth);
$('#hgTrackUiDialog').dialog("option", "width", myWidth);
$('#hgTrackUiDialog').dialog('option' , 'title' ,
hgTracks.trackDb[popUp.trackName].shortLabel+" Track Description");
$('#hgTrackUiDialog').dialog('open');
var buttOk = $('button.ui-state-default');
if ($(buttOk).length === 1)
$(buttOk).focus();
} else {
$('#hgTrackUiDialog').dialog('option' , 'title' ,
hgTracks.trackDb[popUp.trackName].shortLabel+" Track Settings");
$('#hgTrackUiDialog').dialog('open');
}
}
};
///////////////////////////////
//// imageV2 (aka imgTbl) ////
///////////////////////////////
var imageV2 = {
enabled: false, // Will be set to true unless advancedJavascriptFeatures
// is turned off OR if track search of config page
imgTbl: null, // formerly "trackImgTbl" The imgTbl or null if non-imageV2.
inPlaceUpdate: false, // modified based on value of hgTracks.inPlaceUpdate & mapIsUpdateable
mapIsUpdateable:true,
lastTrack: null, // formerly (lastMapItem) this is used to try to keep what the
// last track the cursor passed.
markAsDirtyPage: function ()
{ // Page is marked as dirty so that the back-button knows page doesn't match cart
var dirty = normed($('#dirty'));
if (dirty)
$(dirty).val('true');
},
markAsCleanPage: function ()
{ // Clears signal that history may be out of sync with cart.
var dirty = normed($('#dirty'));
if (dirty)
$(dirty).val('false');
},
isDirtyPage: function ()
{ // returns true if page was marked as dirty
// This will allow the backbutton to be overridden
var dirty = normed($('#dirty'));
if (dirty && $(dirty).val() === 'true')
return true;
return false;
},
manyTracks: function ()
{ // image-reload is slower than whole page reload when there are too many tracks
if (!hgTracks || !hgTracks.trackDb || objKeyCount(hgTracks.trackDb) > 50)
return true;
return false;
},
updateTiming: function (response)
{ // update measureTiming text on current page based on what's in the response
var reg = new RegExp("(.+?)", "g");
var strs = [];
for (var a = reg.exec(response); a && a[1]; a = reg.exec(response)) {
strs.push(a[1]);
}
if (strs.length > 0) {
$('.timing').remove();
for (var ix = strs.length; ix > 0; ix--) {
$('body').prepend(strs[ix - 1]);
}
}
reg = new RegExp("([\\S\\s]+?)");
a = reg.exec(response);
if (a && a[1]) {
$('.trackTiming').replaceWith(a[1]);
}
},
loadSuggestBox: function ()
{
if ($('#positionInput').length) {
suggestBox.init(getDb(), $("#suggestTrack").length > 0,
function (item) {
genomePos.set(item.id, commify(getSizeFromCoordinates(item.id)));
},
function (position) {
genomePos.set(position, commify(getSizeFromCoordinates(position)));
});
// Make sure suggestTrack is visible when user chooses via gene select (#3484).
if ($("#suggestTrack").length) {
$(document.TrackForm || document.TrackHeaderForm).submit(function(event) {
if ($('#hgFindMatches').length) {
vis.makeTrackVisible($("#suggestTrack").val());
}
});
}
}
},
afterImgChange: function (dirty)
{ // Standard things to do when manipulations change image without ajax update.
dragReorder.init();
dragSelect.load(false);
imageV2.highlightRegion();
if (dirty)
imageV2.markAsDirtyPage();
},
afterReload: function (id)
{ // Reload various UI widgets after updating imgTbl map.
dragReorder.init();
dragSelect.load(false);
// Do NOT reload context menu (otherwise we get the "context menu sticks" problem).
// rightClick.load($('#tr_' + id));
if (imageV2.imgTbl.tableDnDUpdate)
imageV2.imgTbl.tableDnDUpdate();
rightClick.reloadFloatingItem();
// Turn on drag scrolling.
if (hgTracks.imgBoxPortal) {
$("div.scroller").panImages();
}
if (imageV2.backSupport) {
if (id) { // The remainder is only needed for full reload
imageV2.markAsDirtyPage(); // vis of cfg change
imageV2.highlightRegion();
return;
}
}
imageV2.loadRemoteTracks();
makeItemsByDrag.load();
imageV2.loadSuggestBox();
imageV2.highlightRegion();
if (imageV2.backSupport) {
imageV2.setInHistory(false); // Set this new position into History stack
} else {
imageV2.markAsDirtyPage();
}
},
updateImgForId: function (html, id, fullImageReload, newJsonRec)
{ // update row in imgTbl for given id.
// return true if we successfully pull slice for id and update it in imgTrack.
var newTr = $(html).find("tr[id='tr_" + id + "']");
if (newTr.length > 0) {
var tr = $(document.getElementById("tr_" + id));
if (tr.length > 0) {
$(tr).html(newTr.children());
// Need to update tr class list too
var classes = $(html).find("tr[id='tr_"+ id + "']")[0].className;
if (classes && classes.length > 0) {
$(tr).removeClass();
$(tr).addClass(classes);
}
// NOTE: Want to examine the png? Uncomment:
//var img = $('#tr_' + id).find("img[id^='img_data_']").attr('src');
//warn("Just parsed image: "+img);
// >1x dragScrolling needs some extra care.
if (hgTracks.imgBoxPortal && (hgTracks.imgBoxWidth > hgTracks.imgBoxPortalWidth)) {
if (hgTracks.imgBoxPortalLeft !== undefined
&& hgTracks.imgBoxPortalLeft !== null) {
$(tr).find('.panImg').css({'left': hgTracks.imgBoxPortalLeft });
$(tr).find('.tdData').css(
{'backgroundPosition': hgTracks.imgBoxPortalLeft});
}
}
// Need to update vis box (in case this is reached via back-button)
if (imageV2.backSupport && fullImageReload) {
// Update abbr so that rows can be resorted properly
var abbr = $(newTr).attr('abbr');
if (abbr) {
$(tr).attr('abbr', abbr);
}
if (newJsonRec)
vis.update(id, vis.enumOrder[newJsonRec.visibility]);
}
return true;
}
}
return false;
},
updateImgForAllIds: function (response, oldJson, newJson)
{ // update all rows in imgTbl based upon navigateInPlace response.
var imgTbl = $('#imgTbl');
// We update rows one at a time
// (b/c updating the whole imgTbl at one time doesn't work in IE).
var id;
for (id in newJson.trackDb) {
var newJsonRec = newJson.trackDb[id];
var oldJsonRec = oldJson.trackDb[id];
if (newJsonRec.visibility === 0) // hidden 'ruler' is in newJson.trackDb!
continue;
if (newJsonRec.type === "remote")
continue;
if (oldJsonRec && oldJsonRec.visibility !== 0 && $('tr#tr_' + id).length === 1) {
// New track replacing old:
if (!imageV2.updateImgForId(response, id, true, newJsonRec))
warn("Couldn't parse out new image for id: " + id);
} else { //if (!oldJsonRec || oldJsonRec.visibility === 0)
// New track seen for the first time
if (imageV2.backSupport) {
$(imgTbl).append("
");
if (!imageV2.updateImgForId(response, id, true, newJsonRec))
warn("Couldn't insert new image for id: " + id);
}
}
}
if (imageV2.backSupport) {
// Removes OLD: those in oldJson but not in newJson
for (id in oldJson.trackDb) {
if ( ! newJson.trackDb[id] )
$(document.getElementById('tr_' + id)).remove();
}
// Need to reorder the rows based upon abbr
dragReorder.sort($(imgTbl));
}
},
updateChromImg: function (response)
{ // Parse out new chrom 'ideoGram' (if available)
// e.g.:
// Larry's regex voodoo:
var a = /]+SRC[^>]+id='chrom'[^>]*)>/.exec(response);
if (a && a[1]) {
var b = /SRC\s*=\s*"([^")]+)"/.exec(a[1]);
if (b && b[1]) {
$('#chrom').attr('src', b[1]);
// Even if we're on the same chrom, ideoMap may change because the label
// on the left changes width depending on band name, and that changes px scaling.
var ideoMapMatch = /