48e64c1e21d90b822aef8b11a2eabfb44321dd50 jnavarr5 Wed Dec 3 16:34:13 2025 -0800 Updating the toggleSelects function to accept a DOM element. Disabling buttons so you aren't taken away from the tutorial by clicking the button it is explaining, refs #34354 diff --git src/hg/js/tutorials/customTrackTutorial.js src/hg/js/tutorials/customTrackTutorial.js index 7f3d534727a..1c78a8887fe 100644 --- src/hg/js/tutorials/customTrackTutorial.js +++ src/hg/js/tutorials/customTrackTutorial.js @@ -1,532 +1,551 @@ /* jshint esnext: true */ /* global Shepherd */ //Creating an IIFE to prevent global variable conflicts (function() { const customTrackTour = new Shepherd.Tour({ defaultStepOptions: { cancelIcon: { enabled: true }, classes: 'class-1 class-2', scrollTo: { behavior: 'smooth', block: 'center' } }, useModalOverlay: true, canClickTarget: false }); // log when a tutorial is started (commented out for now) customTrackTour.on('start', function() { writeToApacheLog("customTrackTutorial start " + getHgsid()); }); var tutorialButtons = { 'back': { action() { return this.back(); }, classes: 'shepherd-button-secondary', text: 'Back' }, 'next': { action() { return this.next(); }, text: 'Next' }, 'quit': { action() { return this.complete(); }, classes: 'shepherd-button-secondary', text: 'Exit' }, 'load_bigWig': { text: 'Load a bigWig example', classes: 'shepherd-button-optional', action (event) { // If button was clicked already, do nothing if (event.currentTarget.textContent === 'bigWig example added'){ return; } // Change button color when clicked if (event && event.currentTarget) { event.currentTarget.style.backgroundColor = '#87CEEB'; // Light blue event.currentTarget.textContent = 'bigWig example added'; } // Add text to the textarea const textarea = document.querySelector('textarea[name="hgct_customText"]'); if (textarea.value.trim() === '') { textarea.value = 'browser position chr21:33031596-33041570\n'; } textarea.value += 'track type=bigWig name="bigWig variableStep" '+ 'description="bigWig variableStep format" visibility=full '+ 'autoScale=off viewLimits=5.0:25.0 color=255,200,0 '+ 'yLineMark=11.76 yLineOnOff=on '+ 'bigDataUrl="http://genome-test.gi.ucsc.edu/~hiram/ctDb/wigVariableStep.bw"\n'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, 'load_bigBed': { text: 'Load a bigBed example', classes: 'shepherd-button-optional', action (event) { // If button was clicked already, do nothing if (event.currentTarget.textContent === 'bigBed example added'){ return; } // Change button color when clicked if (event && event.currentTarget) { event.currentTarget.style.backgroundColor = '#87CEEB'; // Light blue event.currentTarget.textContent = 'bigBed example added'; } // Add text to the textarea const textarea = document.querySelector('textarea[name="hgct_customText"]'); if (textarea.value.trim() === '') { textarea.value = 'browser position chr21:33031596-33041570\n'; } textarea.value += 'track type=bigBed db="hg19" name="bigBed12" type="bigBed 12" '+ 'bigDataUrl="http://genome-test.gi.ucsc.edu/~hiram/ctDb/bigBedType12.bb "'+ 'visibility=pack itemRgb="On"\n'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, 'load_bed': { text: 'Load a BED example', classes: 'shepherd-button-optional', action (event) { // If button was clicked already, do nothing if (event.currentTarget.textContent === 'BED example added'){ return; } // Change button color when clicked if (event && event.currentTarget) { event.currentTarget.style.backgroundColor = '#87CEEB'; // Light blue event.currentTarget.textContent = 'BED example added'; } // Add text to the textarea const textarea = document.querySelector('textarea[name="hgct_customText"]'); if (textarea.value.trim() === '') { textarea.value = 'browser position chr21:33031596-33041570\n'; } textarea.value += 'track name="BED 6 example" priority="40" visibility=pack\n'+ 'chr21 33031596 33033258 item_0 0 -\n'+ 'chr21 33033258 33034920 item_1 100 +\n'+ 'chr21 33034920 33036582 item_2 200 -\n'+ 'chr21 33036582 33038244 item_3 300 +\n'+ 'chr21 33038244 33039906 item_4 400 -\n'+ 'chr21 33039906 33041570 item_5 500 +\n'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, 'load_wig': { text: 'Load a WIG example', classes: 'shepherd-button-optional', action (event) { // If button was clicked already, do nothing if (event.currentTarget.textContent === 'WIG example added'){ return; } // Change button color when clicked if (event && event.currentTarget) { event.currentTarget.style.backgroundColor = '#87CEEB'; // Light blue event.currentTarget.textContent = 'WIG example added'; } // Add text to the textarea const textarea = document.querySelector('textarea[name="hgct_customText"]'); if (textarea.value.trim() === '') { textarea.value = 'browser position chr21:33031596-33041570\n'; } textarea.value += 'track type=wiggle_0 priority="110" name="Wiggle variableStep" '+ 'visibility=full autoScale=off viewLimits=5.0:25.0 color=255,200,0 '+ 'yLineMark=11.76 yLineOnOff=on\n'+ 'variableStep chrom=chr21 span=1108\n'+ '33031597 10.0\n'+ '33032705 12.5\n'+ '33033813 15.0\n'+ '33034921 17.5\n'+ '33036029 20.0\n'+ '33037137 17.5\n'+ '33038245 15.0\n'+ '33039353 12.5\n'+ '33040461 10.0\n'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, 'load_html': { text: 'Load HTML example', classes: 'shepherd-button-optional', action (event) { // If button was clicked already, do nothing if (event.currentTarget.textContent === 'HTML example added'){ return; } // Change button color when clicked if (event && event.currentTarget) { event.currentTarget.style.backgroundColor = '#87CEEB'; // Light blue event.currentTarget.textContent = 'HTML example added'; } // Add text to the textarea const textarea = document.querySelector('textarea[name="hgct_docText"]'); textarea.value = '

Description

\n' + '

\n'+ 'Replace this text with a summary describing the concepts or \n'+ 'analysis represented by your data.

\n'+ '\n'+ '

Methods

\n'+ '

\n'+ 'Replace this text with a description of the methods used to \n'+ 'generate and analyze the data.

\n'+ '\n'+ '

Verification

\n'+ '

\n'+ 'Replace this text with a description of the methods used to \n'+ 'verify the data.

\n'+ '\n'+ '

Credits

\n'+ '

\n'+ 'Replace this text with a list of the individuals and/or \n'+ 'organizations who contributed to the collection and analysis \n'+ 'of the data.

\n'+ '\n'+ '

References

\n'+ '

\n'+ 'Replace this text with a list of relevant literature references \n'+ 'and/or websites that provide background or supporting \n'+ 'information about the data.

'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, 'submit': { text: 'Submit & continue', action() { // Save the tour so it continues after page reload sessionStorage.setItem('customTrackTourActive', 'true'); sessionStorage.setItem('customTrackTourStep', 'after-submit'); // Click the submit button const submitButton = document.getElementById('Submit'); submitButton.click(); } }, 'end': { action() { hideMenu('#help > ul'); return this.complete(); }, //classes: 'shepherd-button-secondary', text: 'Finish' } }; // Function to disable drop-downs, button clicks, and text inputs + // Now accepts a string or a DOM element. function toggleSelects(containerId, enabled) { - const container = document.getElementById(containerId); + const container = typeof containerId === 'string' + ? document.getElementById(containerId) // If true, + : containerId; // else assume it is a DOM element const selectors = ['select', 'button', 'input[type="radio"]', 'input[type="submit"]', 'input[type="checkbox"]', 'input[type="text"]', 'a' ]; - container.querySelectorAll(selectors).forEach(sel => { + container.querySelectorAll(selectors.join(',')).forEach(sel => { if (enabled) { sel.style.pointerEvents = ''; sel.style.opacity = ''; sel.tabIndex = 0; } else { sel.style.pointerEvents = 'none'; // blocks mouse interaction sel.style.opacity = '1'; // keep visual styling normal sel.tabIndex = -1; // skip from keyboard nav } }); } // Function to keep menus visible using a selector function keepMenuVisible(selector) { const menu = document.querySelector(selector); //Make sure the drop-down is visibile menu.style.display = 'block'; menu.style.visibility = 'visible'; // function to keep the menu visibile const makeVisible = () => { menu.style.display = 'block'; menu.style.visibility = 'visible'; }; const events = ['mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'mousemove']; // Add event listeners to keep the menu open events.forEach(event => { menu.addEventListener(event, makeVisible); }); // Add event listeners to the elements of the menu list menu.querySelectorAll('li').forEach(function(item) { events.forEach(event => { item.addEventListener(event, makeVisible); }); }); } // Function to hide the menu function hideMenu(selector) { const menu = document.querySelector(selector); menu.style.display = 'none'; } // Function to add steps to the customTrackTour function customTrackSteps() { customTrackTour.addStep({ title: 'Adding custom data to the Genome Browser', text: 'In this tutorial, we '+ 'will explore how to configure custom tracks '+ 'in the various formats '+ 'available for a track.

'+ 'For a stable and permanent visualization, please consider '+ 'creating a track hub. If you require hosting space, '+ 'the UCSC Genome Browser now provides 10Gb of '+ 'free hosting space '+ 'for each user.', buttons: [tutorialButtons.quit, tutorialButtons.next], id: 'intro' }); customTrackTour.addStep({ title: 'Selecting the genome assembly', text: 'By default, your most recently viewed assembly is selected. '+ 'Alter the drop-down menus to change the genome assembly.', buttons: [tutorialButtons.back, tutorialButtons.next], attachTo: { element: '#genome-selection-table', on: 'bottom' }, id: 'genome-select', when: { show: () => toggleSelects('genome-selection-table', false), hide: () => toggleSelects('genome-selection-table', true) } }); customTrackTour.addStep({ title: 'Text-based custom tracks', text: 'The simplest way to view a custom track is to paste '+ 'the track and data lines in the dialog box. '+ 'A custom track consists of three items:'+ '
    '+ '
  1. browser line: (optional)
    '+ ' Control aspects of the overall '+ ' display window
  2. '+ '
  3. track line:
    '+ ' Defines the attributes for '+ ' the specific data set
  4. '+ '
  5. data lines
  6. '+ '
'+ 'Alternatively, you can store the custom track data in a file on a web server, '+ 'paste the file URL, or upload the file to load the custom track. ', buttons: [tutorialButtons.back, tutorialButtons.load_bed, tutorialButtons.load_wig, tutorialButtons.next], attachTo: { element: '#data-input', on: 'right' }, id: 'dialog-box' }); customTrackTour.addStep({ title: 'Binary-indexed custom tracks', text: 'To view a bigBed, bigWig, or another binary-indexed file, the file must be '+ 'hosted '+ 'on an external server that allows byte-range requests. '+ 'Binary-indexed custom tracks only need a track '+ 'line to define the custom track as it uses the '+ 'bigDataUrl setting to fetch the annotations.'+ '

'+ 'Alternatively, you can paste the URL directly without a "track line" to '+ 'quickly view the file. The benefit in using a track line with a '+ 'bigDataUrl parameter is ' + 'to add other configuration options for the big* file.', buttons: [tutorialButtons.back, tutorialButtons.load_bigBed, tutorialButtons.load_bigWig, tutorialButtons.next], attachTo: { element: '#data-input', on: 'right' }, id: 'bigCustom-tracks' }); customTrackTour.addStep({ title: 'Upload track documentation  (Optional)', text: 'In this dialoge box, you can add HTML that will be shown on the custom '+ 'track\'s description page. This is not required but highly recommended.'+ '

'+ 'An example HTML text is provided, and can '+ 'be edited to fit your needs. ', buttons: [tutorialButtons.back, tutorialButtons.load_html, tutorialButtons.next], attachTo: { element: '#description-input', on: 'right' }, id: 'documentation-box' }); customTrackTour.addStep({ title: 'Submit the custom track', text: 'Once you paste/upload your custom track data, you can use the '+ 'Submit button to send the data to the UCSC Genome Browser. ', buttons: [tutorialButtons.back, tutorialButtons.submit], when: { show: function() { const textarea = document.querySelector('textarea[name="hgct_customText"]'); const isEmpty = textarea.value.trim() === ''; // If the textbox is empty, load a BED track if (isEmpty) { textarea.value = 'browser position chr21:33031596-33041570\n'; textarea.value += 'track type=bigWig name="bigWig variableStep" '+ 'description="bigWig variableStep format" visibility=full '+ 'autoScale=off viewLimits=5.0:25.0 color=255,200,0 '+ 'yLineMark=11.76 yLineOnOff=on '+ 'bigDataUrl="http://genome-test.gi.ucsc.edu/~hiram/ctDb/wigVariableStep.bw"\n'; // Optional: trigger any change events if the page listens for them textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('input', { bubbles: true })); } + const submitButton = document.querySelector('input#Submit').parentElement; + toggleSelects(submitButton, false); + }, + hide: function() { + const submitButton = document.querySelector('input#Submit').parentElement; + toggleSelects(submitButton, true); } }, attachTo: { element: '#Submit', on: 'top' }, id: 'submit' }); } function customTrackResultSteps() { customTrackTour.addStep({ title: 'View your uploaded custom tracks', text: 'This table shows the custom tracks that you uploaded to the UCSC Genome Browser '+ 'from the previous page.

'+ 'Using this table, you can delete any unwanted custom '+ 'tracks. You can also click onto the track names to edit the settings or data.', buttons: [tutorialButtons.next], attachTo: { element: '#resultsTable', on: 'bottom' }, id: 'after-submit', when: { show: () => toggleSelects('resultsTable', false), hide: () => toggleSelects('resultsTable', true) } }); customTrackTour.addStep({ title: '"View in" drop-down menu', text: 'By default, the Genome Browser will take you to view your custom track '+ 'on the main Genome Browser image.

'+ 'Altering this drop-down will let you send '+ 'the data to the Table Browser, Variant Annotation Integrator, or the '+ 'Data Integrator.', buttons: [tutorialButtons.back, tutorialButtons.next], attachTo: { element: '#navSelect', on: 'bottom' }, id: 'navigation-drop-down', when: { show: () => toggleSelects('navForm', false), hide: () => toggleSelects('navForm', true) } }); customTrackTour.addStep({ title: 'Add more tracks to the Genome Browser', text: 'Click this button to return to the previous page to add more custom tracks.', buttons: [tutorialButtons.back, tutorialButtons.next], attachTo: { element: '#addTracksButton', on: 'bottom' }, - id: 'add-tracks' + id: 'add-tracks', + when: { + show: function() { + const submitButton = document.querySelector('input#addTracksButton').parentElement; + toggleSelects(submitButton, false); + }, + hide: function() { + const submitButton = document.querySelector('input#addTracksButton').parentElement; + toggleSelects(submitButton, true); + } + } }); customTrackTour.addStep({ title: 'Additional resources and documentation', text: 'For further examples of using ' + 'custom tracks, please read the Custom Track user guide. You can find examples of simple '+ 'annotation files, BED custom tracks with muliple blocks, loading custom tracks '+ 'via the URL, and more. '+ '

'+ 'You can also contact us if you '+ 'have any questions or issues uploading a custom track. ', attachTo: { element: '#hgCustomHelp', on: 'left-start' }, buttons: [ tutorialButtons.back, tutorialButtons.end ], when: { show: () => { keepMenuVisible('#help > ul'); }, hide: () => hideMenu('#help > ul') }, id: 'additional-help' }); } if (typeof window.customTrackTour === 'undefined') { // Unique ID to verify that you are on the results page customTrackSteps(); //Export the customTrackTour globalally window.customTrackTour = customTrackTour; } // Condition to continue the tutorial once you submit the custom track if (sessionStorage.getItem('customTrackTourActive') === 'true') { const stepToShow = sessionStorage.getItem('customTrackTourStep'); // Clear the session storage sessionStorage.removeItem('customTrackTourActive'); sessionStorage.removeItem('customTrackTourStep'); // Resume tour at the saved step if (stepToShow) { customTrackResultSteps(); customTrackTour.show(stepToShow); } } })();