ef77b08098f12682c3318243d14dfb52cda203f8 angie Mon Apr 25 11:50:43 2016 -0700 Cath's suggestion from #15277#note-190: Use the same width for the select assembly menu as for the position input and center the Go button vertically. Also fixed IE8 clobbering of activeGenomes global. refs #15277 diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js index 3e4181e..f3e9801 100644 --- src/hg/js/hgGateway.js +++ src/hg/js/hgGateway.js @@ -11,35 +11,34 @@ // speciesTree: module that exports draw() function for drawing a phylogenetic tree // (and list of hubs above the tree, if any) in a pre-existing SVG element -- see // hg/hgGateway/hgGateway.html. // rainbow: module that exports draw() function and colors. draw() adds stripes using // a spectrum of colors that are associated to species groups. The hgGateway view code // uses coordinates of stripes within the tree image to create a corresponding "rainbow" // slider bar to the left of the phylogenetic tree container. // autocompleteCat: customized JQuery autocomplete plugin that includes watermark and // can display results broken down by category (for example, genomes from various // assembly hubs and native genomes). // hgGateway: module of mostly view/controller code (model state comes directly from server). -// Globals: -/* globals calculateHgTracksWidth */ // pragma for jshint; function is defined in utils.js -var dbDbTree = dbDbTree || undefined; -var activeGenomes = activeGenomes || undefined; -var cart = cart || undefined; +// Globals (pragma for jshint): +/* globals dbDbTree, activeGenomes, cart */ +/* globals calculateHgTracksWidth */ // function is defined in utils.js + function svgCreateEl(type, config) { // Helper function for creating a new SVG element and initializing its // properties and attributes. Type is something like 'rect', 'text', 'g', etc; // config is an object like { id: 'newThingie', x: 0, y: 10, title: 'blah blah' }. var svgns = 'http://www.w3.org/2000/svg'; var xlinkns = 'http://www.w3.org/1999/xlink'; var el = document.createElementNS(svgns, type); var title, titleEl; if (el) { _.forEach(config, function(value, setting) { if (setting === 'textContent') { // Text content (the text in a text element or title element) is a property: el.textContent = value; } else if (setting === 'href') { @@ -768,30 +767,32 @@ 'existing browser to ' + '' + ' FireFox or ' + '' + 'Chrome.' + '

'; // Globals // Set this to true to see server requests and responses in the console. var debugCartJson = false; // This is a global (within wrapper function scope) so event handlers can use it // without needing to bind functions. var scrollbarWidth = 0; // This holds everything we need to know to draw the page: taxId, db, hubs, description etc. var uiState = {}; + // This is dbDbTree after pruning -- null if dbDbTree has no children left + var prunedDbDbTree; function setupFavIcons() { // Set up onclick handlers for shortcut buttons and labels var i, name, taxId, onClick; for (i = 0; i < favIconTaxId.length; i++) { name = favIconTaxId[i][0]; taxId = favIconTaxId[i][1]; // When user clicks on icon, set the taxId (default database); // scroll the image to that species and clear the species autocomplete input. onClick = setTaxId.bind(null, taxId, null, true, true); // Onclick for both the icon and its sibling label: $('.jwIconSprite' + name).parent().children().click(onClick); } } @@ -986,41 +987,58 @@ resizeSliderIcon($sliderIcon, svgHeight, sliderBarHeight); $sliderIcon.draggable({ axis: 'y', containment: '#speciesGraphic', drag: onDragSlider }); $speciesPicker.scroll(onScrollImage); } function findScrollbarWidth() { var widthPlain = $("#sbTestContainerDPlain").width(); var widthInsideScroll = $("#sbTestContainerDInsideScroll").width(); $('#sbTestContainer').hide(); return widthPlain - widthInsideScroll; } + function updateGoButtonPosition() { + // If there's enough room for the Go button to be to the right of the inputs, + // set its height to the midpoint of theirs. + var $goButton = $('.jwGoButtonContainer'); + var goOffset = $goButton.offset(); + var menuOffset = $('#selectAssembly').offset(); + var inputOffset = $('#positionInput').offset(); + var verticalMidpoint = (menuOffset.top + inputOffset.top) / 2; + if (goOffset.left > inputOffset.left) { + $goButton.offset({top: verticalMidpoint }); + } else { + // If the window shrinks and there's no longer room for the button, undo the above. + $goButton.css('top', 0); + } + } + function setRightColumnWidth() { // Adjust the width of the "Find Position" section so it fits to the right of the // "Browse/Select Species" section. var ieFudge = scrollbarWidth ? scrollbarWidth + 4 : 0; var extraFudge = 4; var rightColumnWidth = ($('#pageContent').width() - $('#selectSpeciesSection').width() - ieFudge - extraFudge); if (rightColumnWidth >= 400) { $('#findPositionSection').width(rightColumnWidth); } + updateGoButtonPosition(); } function setSpeciesPickerSizes(svgWidth, svgHeight) { // Adjust widths and heights of elements in #speciesPicker according to svg size. $('#speciesTree').width(svgWidth); $('#speciesTree').height(svgHeight); $('#speciesTreeContainer').height(svgHeight); // Make #speciesTreeContainer skinnier if a scrollbar is taking up space // in #speciesPicker. var leftover = ($("#speciesPicker").width() - scrollbarWidth); $("#speciesTreeContainer").width(leftover); } function highlightLabel(selectedName, scrollToItem) { // Highlight the selected species. @@ -1089,102 +1107,81 @@ } } } else { // parent node: splice out any child nodes with no active leaves for (i = kids.length - 1; i >= 0; i--) { if (pruneInactive(kids[i], activeGenomes, activeTaxIds)) { hasActiveLeaf = true; } else { kids.splice(i, 1); } } } return hasActiveLeaf; } - function drawSpeciesPicker() { + function drawSpeciesPicker(dbDbTree) { // If dbDbTree is nonempty and SVG is supported, draw the tree; if SVG is not supported, // use the space to suggest that the user install a better browser. // If dbDbTree doesn't exist, leave the "Represented Species" section hidden. var svg, spTree, stripeTops; if (dbDbTree) { if (document.createElementNS) { // Draw the phylogenetic tree and do layout adjustments svg = document.getElementById('speciesTree'); spTree = speciesTree.draw(svg, dbDbTree, uiState.hubs, { onClickSpeciesName: 'hgGateway.onClickSpeciesLabel', onClickHubName: 'hgGateway.onClickHubName', hgHubConnectUrl: 'hgHubConnect?hgsid=' + window.hgsid, containerWidth: $('#speciesPicker').width() }); setSpeciesPickerSizes(spTree.width, spTree.height); highlightLabelForDb(uiState.db, uiState.taxId); stripeTops = rainbow.draw(svg, dbDbTree, spTree.yTree, spTree.height, spTree.leafTops); } else { $('#speciesTreeContainer').html(getBetterBrowserMessage); } $('#representedSpeciesTitle').show(); $('#speciesGraphic').show(); - if (dbDbTree) { + if (dbDbTree && document.createElementNS) { // This needs to be done after things are visible so the slider gets the // right position. initRainbowSlider(spTree.height, rainbow.colors, stripeTops); } } } function onSelectGene(item) { // Set the position from an autocomplete result; // set hgFindMatches and make sure suggestTrack is in pack mode for highlighting the match. var newPos = item.id; var settings; $('#positionDisplay').text(newPos); if (uiState.suggestTrack) { settings = { 'hgFind.matches': item.internalId }; settings[uiState.suggestTrack] = 'pack'; cart.send({ cgiVar: settings }); cart.flush(); } // Overwrite the selected item w/actual position after the autocomplete plugin is done: function overwriteWithPos() { $('#positionInput').val(newPos); } window.setTimeout(overwriteWithPos, 0); } - function updateGoButtonPosition() { - // If there's room for the go button to appear to the right of #selectAssembly and - // #positionInput, align the top of the go button with the bottom of #selectAssembly. - // Otherwise let it hang out below. - var $fpic = $('#findPosInputContainer'); - var fpicOffset = $fpic.offset(); - var fpicRight = fpicOffset.left + $fpic.width(); - var $button = $('.jwGoButtonContainer'); - var buttonOffset = $button.offset(); - var $select; - if (buttonOffset.left > fpicRight) { - // Align button top with select bottom. - $select = $('#selectAssembly'); - buttonOffset.top = $select.offset().top + $select.height(); - } else { - // Button wraps around to below inputs; remove any previous vertical offsetting. - buttonOffset.top = fpicOffset.top + $fpic.height() + 10; - } - $button.offset(buttonOffset); - } - function setAssemblyOptions(uiState) { var assemblySelectLabel = 'Assembly'; if (uiState.dbOptions) { var html = '', option, i, selected; for (i = 0; i < uiState.dbOptions.length; i++) { option = uiState.dbOptions[i]; selected = (option.value === uiState.db) ? 'selected ' : ''; html += ''; } $('#selectAssembly').html(html); } if (uiState.genomeLabel) { if (uiState.hubUrl && uiState.genomeLabel.indexOf('Hub') < 0) { assemblySelectLabel = uiState.genomeLabel + ' Hub Assembly'; @@ -1239,51 +1236,67 @@ } function updateDescription(description) { // We got the contents of a db's description.html -- tweak its format to fit // the new design. $('#descriptionText').html(description); tweakDescriptionPhotoWidth(); // Apply JWest formatting to all anchors in description. // We can't simply style all tags that way because autocomplete uses 's. $('#descriptionText a').addClass('jwAnchor'); // Apply square bullet style to all ul's in description. $('#descriptionText ul').addClass('jwNoBullet'); $('#descriptionText li').addClass('jwSquareBullet'); } + function initFindPositionContents() { + // Unhide contents of Find Position section and adjust layout. + $('#findPositionContents').show(); + // Set assembly menu's width to same as position input. + var posWidth = $('#positionInput').outerWidth(); + var $select = $('#selectAssembly'); + $select.outerWidth(posWidth); + // For some reason, it doesn't set it to posWidth, it sets it to posWidth-2... + // detect and adjust. + var weirdDiff = posWidth - $select.outerWidth(); + if (weirdDiff) { + $select.outerWidth(posWidth + weirdDiff); + } + updateGoButtonPosition(); + } + function updateFindPositionSection(uiState) { + // Update the assembly menu, positionInput and description. var suggestUrl = null; if (uiState.suggestTrack) { suggestUrl = 'hgSuggest?db=' + uiState.db + '&prefix='; } setAssemblyOptions(uiState); if (uiState.position) { $('#positionDisplay').text(uiState.position); } autocompleteCat.init($('#positionInput'), { baseUrl: suggestUrl, watermark: positionWatermark, onSelect: onSelectGene, enterSelectsIdentical: true, onEnterTerm: goToHgTracks }); setAssemblyDescriptionTitle(uiState.db, uiState.genome); updateDescription(uiState.description); - if (uiState.db) { - $('#findPositionContents').show(); + if (uiState.db && $('#findPositionContents').css('display') === 'none') { + initFindPositionContents(); } - updateGoButtonPosition(); } function removeDups(inList, isDup) { // Return a list with only unique items from inList, using isDup(a, b) -> true if a =~ b var inLength = inList.length; // inListDups is an array of boolean flags for marking duplicates, parallel to inList. var inListDups = []; var outList = []; var i, j; for (i = 0; i < inLength; i++) { // If something has already been marked as a duplicate, skip it. if (! inListDups[i]) { // the first time we see a value, add it to outList. outList.push(inList[i]); for (j = i+1; j < inLength; j++) { @@ -1356,31 +1369,31 @@ } function updateStateAndPage(jsonData) { // Update uiState with new values and update the page. var hubsChanged = !_.isEqual(jsonData.hubs, uiState.hubs); // In rare cases, there can be a genome (e.g. Baboon) with multiple species/taxIds // (e.g. Papio anubis for papAnu1 vs. Papio hamadryas for papHam1). Changing the // db can result in changing the taxId too. In that case, update the highlighted // species in the tree image. if (jsonData.taxId !== uiState.taxId) { highlightLabel('textEl_' + jsonData.taxId, false); } _.assign(uiState, jsonData); updateFindPositionSection(uiState); if (hubsChanged) { - drawSpeciesPicker(); + drawSpeciesPicker(prunedDbDbTree); } } function handleRefreshState(jsonData) { if (checkJsonData(jsonData, 'handleRefreshState')) { updateStateAndPage(jsonData); } } function handleSetDb(jsonData) { // Handle the server's response to cartJson command setDb or setHubDb if (checkJsonData(jsonData, 'handleSetDb') && trackHubSkipHubName(jsonData.db) === trackHubSkipHubName(uiState.db)) { updateStateAndPage(jsonData); } else { @@ -1553,52 +1566,51 @@ function init() { // Boot up the page; initialize elements and install event handlers. var searchObj = {}; // We need a bound function to pass into autocompleteCat.init below; // however, autocompleteFromTree is even slower than drawing the tree because of // all the copying. So bind now, fill in searchObj later. var processSpeciesResults = processSpeciesAutocompleteItems.bind(null, searchObj); cart.setCgi('hgGateway'); cart.debug(debugCartJson); // Get state from cart cart.send({ getUiState: {} }, handleRefreshState); cart.flush(); // Prune inactive genomes from dbDbTree. var activeTaxIds = _.invert(activeGenomes); + prunedDbDbTree = dbDbTree; if (dbDbTree && ! pruneInactive(dbDbTree, activeGenomes, activeTaxIds)) { - // no more dbDbTree descendants left after pruning - dbDbTree = null; + prunedDbDbTree = null; } // When page has loaded, do layout adjustments and initialize event handlers. $(function() { scrollbarWidth = findScrollbarWidth(); setRightColumnWidth(); setupFavIcons(); autocompleteCat.init($('#speciesSearch'), { baseUrl: 'hgGateway?hggw_term=', watermark: speciesWatermark, onSelect: setDbFromAutocomplete, onServerReply: processSpeciesResults, enterSelectsIdentical: true }); $('#selectAssembly').change(onChangeDbMenu); $('#positionDisplay').click(onClickCopyPosition); $('#copyPosition').click(onClickCopyPosition); $('.jwGoButtonContainer').click(goToHgTracks); $(window).resize(setRightColumnWidth.bind(null, scrollbarWidth)); - $(window).resize(updateGoButtonPosition); replaceHgsidInLinks(); // Fill in searchObj here once everything is displayed. - autocompleteFromTree(dbDbTree, searchObj); + autocompleteFromTree(prunedDbDbTree, searchObj); }); } return { init: init, // For use by speciesTree.draw SVG (text-only onclick): onClickSpeciesLabel: onClickSpeciesLabel, onClickHubName: onClickHubName }; }()); // hgGateway hgGateway.init();