diff --git a/app/assets/stylesheets/partials/_loading_spinner.scss b/app/assets/stylesheets/partials/_loading_spinner.scss index 8439a41..a7ad6dc 100644 --- a/app/assets/stylesheets/partials/_loading_spinner.scss +++ b/app/assets/stylesheets/partials/_loading_spinner.scss @@ -8,7 +8,7 @@ } // Pagination overlay when loading -[busy]:not([no-spinner]) { +.spinner { position: relative; min-height: 400px; display: block; diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index d638c76..b8f4ea3 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -22,6 +22,19 @@ def link_to_result(result) end end + def link_to_tab(target) + if @active_tab == target.downcase + link_to target, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: target.downcase)), + aria: { current: "page" }, + class: "active tab-link", + data: { turbo_frame: "search-results", turbo_action: "advance" } + else + link_to target, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: target.downcase)), + class: "tab-link", + data: { turbo_frame: "search-results", turbo_action: "advance" } + end + end + def view_online(result) return unless result[:source_link].present? diff --git a/app/javascript/loading_spinner.js b/app/javascript/loading_spinner.js index 1c35c39..98186dc 100644 --- a/app/javascript/loading_spinner.js +++ b/app/javascript/loading_spinner.js @@ -1,3 +1,19 @@ +// Update the tab UI to reflect the newly-requested state. This function is called +// by a click event handler in the tab UI. It follows a two-step process: +function swapTabs(new_target) { + // 1. Reset all tabs to base condition + document.querySelectorAll('.tab-link').forEach((tab) => { + tab.classList.remove('active'); + tab.removeAttribute('aria-current'); + }); + // 2. Add "active" class and aria-current attribute to the newly-active tab link + const currentTabLink = document.querySelector(`.tab-link[href*="tab=${new_target}"]`); + if (currentTabLink) { + currentTabLink.classList.add('active'); + currentTabLink.setAttribute('aria-current', 'page'); + } +} + // Loading spinner behavior for pagination (Turbo Frame updates) document.addEventListener('turbo:frame-render', function(event) { if (window.pendingFocusAction === 'pagination') { @@ -23,16 +39,8 @@ document.addEventListener('turbo:frame-render', function(event) { // console.log(`Updated tab input value to: ${queryParam}`); } - // update active tab styling - // remove active class from all tabs - document.querySelectorAll('.tab-link').forEach((tab) => { - tab.classList.remove('active'); - }); - // add active class to current tab - const currentTabLink = document.querySelector(`.tab-link[href*="tab=${queryParam}"]`); - if (currentTabLink) { - currentTabLink.classList.add('active'); - } + // Remove the spinner now that things are ready + document.getElementById('search-results').classList.remove('spinner'); // Clear the pending action window.pendingFocusAction = null; @@ -51,7 +59,17 @@ document.addEventListener('click', function(event) { // Handle tab clicks if (clickedElement.closest('.tab-navigation')) { + const clickedParams = new URLSearchParams(clickedElement.search); + const newTab = clickedParams.get('tab'); + + // Throw the spinner on the search results immediately + document.getElementById('search-results').classList.add('spinner'); + + // Position the window at the top of the results window.scrollTo({ top: 0, behavior: 'smooth' }); + + swapTabs(newTab); + window.pendingFocusAction = 'tab'; } }); diff --git a/app/views/search/_source_tabs.html.erb b/app/views/search/_source_tabs.html.erb index 7f02c4c..0e2978d 100644 --- a/app/views/search/_source_tabs.html.erb +++ b/app/views/search/_source_tabs.html.erb @@ -1,14 +1,8 @@ \ No newline at end of file diff --git a/test/helpers/search_helper_test.rb b/test/helpers/search_helper_test.rb index ccb83f6..3ee91aa 100644 --- a/test/helpers/search_helper_test.rb +++ b/test/helpers/search_helper_test.rb @@ -199,6 +199,27 @@ class SearchHelperTest < ActionView::TestCase assert_equal 'Sample Document Title', link_to_result(result) end + test 'link_to_tab builds a link without an aria attribute when that tab is not active' do + @active_tab = 'bar' + actual = link_to_tab('Foo') + + assert_select Nokogiri::HTML::Document.parse( actual ), 'a' do |link| + assert_select '[class*=?]', 'active', count: 0 + assert_select '[class*=?]', 'tab-link' + assert_select '[aria-current=?]', 'page', count: 0 + end + end + + test 'link_to_tab builds a link that includes an aria attribute when that tab is active' do + @active_tab = 'foo' + actual = link_to_tab('Foo') + + assert_select Nokogiri::HTML::Document.parse( actual ), 'a' do |link| + assert_select link, '[class*=?]', 'active' + assert_select link, '[aria-current=?]', 'page', count: 1 + end + end + test 'primo_search_url generates correct Primo URL' do query = 'machine learning' expected_url = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cmachine+learning&vid=01MIT_INST%3AMIT'