Skip to content

Commit 32f45ed

Browse files
authored
Merge pull request #265 from MITLibraries/use-135-sticky-tabs
Adds persistence to Tab selection across searches
2 parents 9267108 + a27f0e3 commit 32f45ed

File tree

10 files changed

+189
-22
lines changed

10 files changed

+189
-22
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ See `Optional Environment Variables` for more information.
8282
- `PRIMO_SCOPE`: The Primo Search API `scope` param (set to `cdi` for CDI-scoped results).
8383
- `PRIMO_TAB`: The Primo Search API `tab` param (typically `all`).
8484
- `PRIMO_VID`: The Primo Search API `vid` (or 'view ID`) param.
85+
- `SECRET_KEY_BASE`: You can generate this via `bin/rails secret`. Please do not re-use the production value locally.
8586
- `SYNDETICS_PRIMO_URL`: The Syndetics API URL for Primo. This is used to construct thumbnail URLs.
8687
- `TIMDEX_GRAPHQL`: Set this to the URL of the GraphQL endpoint. There is no default value in the application.
8788

@@ -96,6 +97,7 @@ See `Optional Environment Variables` for more information.
9697
- `FEATURE_GEODATA`: Enables features related to geospatial data discovery. Setting this variable to `true` will trigger geodata
9798
mode. Note that this is currently intended _only_ for the geodata app and
9899
may have unexpected consequences if applied to other TIMDEX UI apps.
100+
- `FEATURE_SIMULATE_SEARCH_LATENCY`: DO NOT SET IN PRODUCTION. Set to ensure a minimum of a one second delay in returning search results. Useful to see spinners/loaders. Only introduces delay for results that take less than one second to complete.
99101
- `FILTER_ACCESS_TO_FILES`: The name to use instead of "Access to files" for that filter / aggregation.
100102
- `FILTER_CONTENT_TYPE`: The name to use instead of "Content type" for that filter / aggregation.
101103
- `FILTER_CONTRIBUTOR`: The name to use instead of "Contributor" for that filter / aggregation.

app/assets/stylesheets/partials/_search.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,8 @@
196196
.tab-link {
197197
color: #fff;
198198
}
199+
200+
/* temp style to visualize active tab. Save us from this Dave! */
201+
#tabs .active{
202+
background-color: red;
203+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
11
class ApplicationController < ActionController::Base
22
helper Mitlibraries::Theme::Engine.helpers
3+
4+
# Set active tab based on feature flag and params
5+
# Also stores the last used tab in a cookie for future searches when passed via params.
6+
# We set this in a session cookie to persist user preference across searches.
7+
# Clicking on a different tab will update the cookie.
8+
def set_active_tab
9+
@active_tab = if Feature.enabled?(:geodata)
10+
# Determine which tab to load - default to primo unless gdt is enabled
11+
'geodata'
12+
elsif params[:tab].present?
13+
# If params[:tab] is set, use it and set session
14+
cookies[:last_tab] = params[:tab]
15+
elsif cookies[:last_tab].present?
16+
# Otherwise, check for last used tab in session
17+
cookies[:last_tab]
18+
else
19+
# Default behavior when no tab is specified in params or session
20+
cookies[:last_tab] = 'primo'
21+
end
22+
end
323
end
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
class BasicSearchController < ApplicationController
2+
before_action :set_active_tab
3+
24
def index; end
35
end

app/controllers/search_controller.rb

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
class SearchController < ApplicationController
22
before_action :validate_q!, only: %i[results]
3+
before_action :set_active_tab, only: %i[results]
4+
around_action :sleep_if_too_fast, only: %i[results]
35

46
before_action :validate_geobox_presence!, only: %i[results]
57
before_action :validate_geobox_range!, only: %i[results]
@@ -13,34 +15,41 @@ def results
1315
# inject session preference for boolean type if it is present
1416
params[:booleanType] = cookies[:boolean_type] || 'AND'
1517

16-
# Determine which tab to load - default to primo unless gdt is enabled
17-
@active_tab = if Feature.enabled?(:geodata)
18-
'gdt' # Keep existing GDT behavior unchanged
19-
else
20-
params[:tab] || 'primo' # Default to primo for new tabbed interface
21-
end
22-
23-
# Include the active tab in the enhanced query so it's available for pagination and other uses.
24-
params[:tab] = @active_tab unless Feature.enabled?(:gdt)
2518
@enhanced_query = Enhancer.new(params).enhanced_query
2619

2720
# Route to appropriate search based on active tab
28-
if Feature.enabled?(:geodata)
29-
# Keep existing GDT behavior unchanged
21+
case @active_tab
22+
when 'primo'
23+
load_primo_results
24+
when 'timdex'
25+
load_timdex_results
26+
when 'geodata'
3027
load_gdt_results
3128
render 'results_geo'
32-
else
33-
case @active_tab
34-
when 'primo'
35-
load_primo_results
36-
when 'timdex'
37-
load_timdex_results
38-
end
3929
end
4030
end
4131

4232
private
4333

34+
# Sleep to simulate latency for testing loading indicators when responses are fast
35+
def sleep_if_too_fast
36+
start_time = Time.now
37+
38+
yield
39+
40+
end_time = Time.now
41+
duration = end_time - start_time
42+
43+
return unless Feature.enabled?(:simulate_search_latency)
44+
45+
Rails.logger.debug "Action #{action_name} from controller #{controller_name} took #{duration.round(2)} seconds to execute."
46+
47+
return unless duration < 1
48+
49+
Rails.logger.debug("Sleeping for #{1 - duration}")
50+
sleep(1 - duration)
51+
end
52+
4453
def load_gdt_results
4554
query = QueryBuilder.new(@enhanced_query).query
4655

@@ -112,7 +121,7 @@ def query_timdex(query)
112121

113122
# Builder hands off to wrapper which returns raw results here.
114123
Rails.cache.fetch("#{cache_key}/#{@active_tab}", expires_in: 12.hours) do
115-
raw = if @active_tab == 'gdt'
124+
raw = if @active_tab == 'geodata'
116125
execute_geospatial_query(query)
117126
elsif @active_tab == 'timdex'
118127
TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query)

app/javascript/loading_spinner.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,35 @@ document.addEventListener('turbo:frame-render', function(event) {
88
}
99
// Clear the pending action
1010
window.pendingFocusAction = null;
11-
}
11+
};
12+
13+
if (window.pendingFocusAction === 'tab') {
14+
// console.log("Tab change detected - focusing on first result");
15+
16+
const urlParams = new URLSearchParams(window.location.search);
17+
const queryParam = urlParams.get('tab');
18+
const searchInput = document.querySelector('input[name="tab"]');
19+
20+
// update hidden form element to ensure correct tab is used for subsequent searches
21+
if (searchInput && queryParam != null) {
22+
searchInput.value = queryParam;
23+
// console.log(`Updated tab input value to: ${queryParam}`);
24+
}
25+
26+
// update active tab styling
27+
// remove active class from all tabs
28+
document.querySelectorAll('.tab-link').forEach((tab) => {
29+
tab.classList.remove('active');
30+
});
31+
// add active class to current tab
32+
const currentTabLink = document.querySelector(`.tab-link[href*="tab=${queryParam}"]`);
33+
if (currentTabLink) {
34+
currentTabLink.classList.add('active');
35+
}
36+
37+
// Clear the pending action
38+
window.pendingFocusAction = null;
39+
};
1240
});
1341

1442
document.addEventListener('click', function(event) {
@@ -20,4 +48,26 @@ document.addEventListener('click', function(event) {
2048
window.scrollTo({ top: 0, behavior: 'smooth' });
2149
window.pendingFocusAction = 'pagination';
2250
}
23-
});
51+
52+
// Handle tab clicks
53+
if (clickedElement.closest('.tab-navigation')) {
54+
window.scrollTo({ top: 0, behavior: 'smooth' });
55+
window.pendingFocusAction = 'tab';
56+
}
57+
});
58+
59+
// On Turbo Frame render, update the search input value to match the current URL parameter
60+
// This ensures that after using the back button the search input reflects the correct query
61+
document.addEventListener('turbo:load', function(event) {
62+
// update form element name 'q' to use current url paramater `q`
63+
// console.log(`turbo:frame-render event detected for frame: ${event.target.id}`);
64+
const urlParams = new URLSearchParams(window.location.search);
65+
const queryParam = urlParams.get('q');
66+
const searchInput = document.querySelector('input[name="q"]');
67+
if (searchInput && queryParam != null) {
68+
searchInput.value = queryParam;
69+
// console.log(`Updated search input value to: ${queryParam}`);
70+
}
71+
});
72+
73+

app/models/feature.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#
3434
class Feature
3535
# List of all valid features in the application
36-
VALID_FEATURES = %i[geodata boolean_picker].freeze
36+
VALID_FEATURES = %i[geodata boolean_picker simulate_search_latency].freeze
3737

3838
# Check if a feature is enabled by name
3939
#

app/views/search/_form.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h3>What can we help you find?</h3>
33
<div class="form-wrapper">
44
<input id="basic-search-main" type="search" class="field field-text basic-search-input" name="q" title="Keyword anywhere" placeholder="Search for anything" value="<%= params[:q] %>" required>
5+
<input id="tab-to-target" type="hidden" name="tab" value="<%= @active_tab %>">
56
<button type="submit" class="btn button-primary">Search</button>
67
</div>
78
</form>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Be sure to restart your server when you modify this file.
2+
# Note, you must set `SECRET_KEY_BASE` environment variable before running the application or sessions will not work.
3+
# Changing `SECRET_KEY_BASE` will invalidate all existing sessions.
4+
# You can generate a new secret key by running `bin/rails secret` command.
5+
Rails.application.config.session_store :cookie_store, key: '_use_session', secure: false, same_site: :strict
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require 'test_helper'
2+
3+
class ApplicationControllerTest < ActionDispatch::IntegrationTest
4+
test 'set_active_tab sets default to primo when no feature flag or params' do
5+
assert_nil cookies[:last_tab]
6+
7+
get root_path
8+
assert_select '#tab-to-target' do
9+
assert_select '[value=?]', 'primo'
10+
refute_select '[value=?]', 'geodata'
11+
refute_select '[value=?]', 'timdex'
12+
end
13+
14+
assert_equal cookies[:last_tab], 'primo'
15+
end
16+
17+
test 'set_active_tab sets to geodata when feature flag enabled' do
18+
skip 'Geodata uses a different form so we do no set the tab this way'
19+
ClimateControl.modify FEATURE_GEODATA: 'true' do
20+
get root_path
21+
assert_select '#tab-to-target' do
22+
refute_select '[value=?]', 'primo'
23+
assert_select '[value=?]', 'geodata'
24+
refute_select '[value=?]', 'timdex'
25+
end
26+
end
27+
end
28+
29+
test 'set_active_tab sets to geodata when feature flag enabled even if param is passed' do
30+
skip 'Geodata uses a different form'
31+
ClimateControl.modify FEATURE_GEODATA: 'true' do
32+
get root_path, params: { tab: 'primo' }
33+
assert_select '#tab-to-target' do
34+
refute_select '[value=?]', 'primo'
35+
assert_select '[value=?]', 'geodata'
36+
refute_select '[value=?]', 'timdex'
37+
end
38+
end
39+
end
40+
41+
test 'set_active_tab sets to param tab when provided' do
42+
get root_path, params: { tab: 'timdex' }
43+
44+
assert_select '#tab-to-target' do
45+
refute_select '[value=?]', 'primo'
46+
refute_select '[value=?]', 'geodata'
47+
assert_select '[value=?]', 'timdex'
48+
end
49+
end
50+
51+
test 'set_active_tab sets to param tab when provided even if cookie is set and updates cookie' do
52+
cookies[:last_tab] = 'timdex'
53+
get root_path, params: { tab: 'geodata' }
54+
55+
assert_select '#tab-to-target' do
56+
refute_select '[value=?]', 'primo'
57+
assert_select '[value=?]', 'geodata'
58+
refute_select '[value=?]', 'timdex'
59+
end
60+
61+
assert_equal cookies[:last_tab], 'geodata'
62+
end
63+
64+
test 'set_active_tab uses cookie last_tab when no param provided' do
65+
cookies[:last_tab] = 'timdex'
66+
get root_path
67+
assert_select '#tab-to-target' do
68+
refute_select '[value=?]', 'primo'
69+
refute_select '[value=?]', 'geodata'
70+
assert_select '[value=?]', 'timdex'
71+
end
72+
end
73+
end

0 commit comments

Comments
 (0)