Skip to content

Commit d965215

Browse files
committed
Redesign filter sidebar
Why these changes are being introduced: The filter sidebar can become unwieldy, as all available filters are shown at all times. Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/GDT-127 * https://mitlibraries.atlassian.net/browse/GDT-173 * https://mitlibraries.atlassian.net/browse/GDT-174 How this addresses that need: This restyles the filter sidebar as dropdown menus. In so doing, it also changes some behavior in the filter logic: * Number of results is listed in the filter sidebar header. * Clicking an applied filter removes that filter (this replaces the previous removal mechanism, which removed all applied filters). * Applied filters are highlighted with an `x` symbol, to indicate that it can be removed on click. (Previously, the 'applied' filter was added, but we hadn't done anything with it.) * The first filter dropdown in the sidebar is always open, even if no filters are applied. Not addressed in this commit: * Darcy asked that we persist menu state between page loads, such that an open menu always remains open unless a user closes it manually (e.g., if all filters have been removed in that category). This proved relatively complex, so I opened GDT-173 to address it. * There are some visual differences in how the sidebar appears in smaller viewports. This is covered in GDT-174. Side effects of this change: * Some tests have been updated to reflect the new logic. * One potential a11y issue with filter categories is having to navigate through a bunch of filter opens before getting to the next category. We might want to add skip-content links if this feels onerous. * The JS for the dropdown menus is so minimal that I included it in the view as a `script` tag. * The filter container is now above the results in the DOM.
1 parent 37c4122 commit d965215

File tree

9 files changed

+209
-59
lines changed

9 files changed

+209
-59
lines changed

app/assets/stylesheets/partials/_filters.scss

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,107 @@
11
#filters {
2-
h3 {
2+
.category {
3+
position: relative;
4+
margin-bottom: 0.5em;
5+
border: 1px solid $gray-l3;
6+
border-radius: 3px;
7+
}
8+
9+
button.filter-label {
10+
display: flex;
11+
justify-content: space-between;
12+
width: 100%;
13+
text-align: left;
314
margin-bottom: 0;
15+
padding: 1rem;
16+
border: 0;
17+
color: $black;
18+
background-color: $gray-l4;
19+
font-size: $fs-small;
20+
font-weight: $fw-bold;
21+
22+
&::after {
23+
font-family: FontAwesome;
24+
content: '\f054';
25+
}
26+
&.expanded {
27+
border: 0;
28+
border-radius: 0;
29+
border-bottom: 1px solid $gray-l3;
30+
& + .filter-options {
31+
max-height: 200px;
32+
overflow-y: scroll;
33+
scrollbar-gutter: auto;
34+
scroll-behavior: auto;
35+
}
36+
}
37+
&.expanded:after {
38+
font-family: FontAwesome;
39+
content: '\f078';
40+
}
41+
&::-webkit-details-marker {
42+
display: none;
43+
}
444
}
545

6-
.category {
7-
margin-bottom: 1em;
46+
.filter-options {
47+
max-height: 0;
48+
transition: max-height 0.5s ease-in-out;
49+
overflow: hidden;
850
}
951

1052
ul.category-terms {
1153
border-collapse: separate;
1254
border-spacing: 0px 4px;
13-
display: table;
14-
margin-bottom: 0;
15-
table-layout: fixed;
16-
width: 100%;
55+
margin: 1rem;
56+
margin-bottom: 2rem;
1757

1858
li.term {
19-
display: contents;
59+
display: block;
60+
width: 100%;
61+
float: none;
62+
margin: 0;
63+
font-size: $fs-small;
2064

2165
a {
22-
display: table-row;
66+
display: block;
67+
width: 100%;
2368
text-decoration: none;
2469

25-
&:hover,
26-
&:focus,
27-
&.applied {
28-
background: #000;
29-
color: #fff;
30-
}
31-
3270
span {
3371
display: table-cell;
3472
padding: 2px;
3573

36-
&.name {
37-
width: 75%;
38-
text-decoration: underline;
39-
}
74+
&.name {
75+
width: 100%;
76+
color: $blue;
77+
}
4078

41-
&.count {
42-
min-width: 25%;
43-
text-align: right;
44-
text-decoration: none;
79+
&.count {
80+
text-align: right;
81+
text-decoration: none;
82+
}
83+
}
84+
85+
&:hover,
86+
&:focus {
87+
background: #000;
88+
span {
89+
color: #fff;
90+
}
91+
}
92+
&.applied {
93+
.name {
94+
color: #000;
95+
&:hover,
96+
&:focus {
97+
color: #fff;
4598
}
99+
}
100+
.name::after {
101+
font-family: FontAwesome;
102+
margin-left: 0.5rem;
103+
content: '\f00d';
104+
}
46105
}
47106
}
48107
}

app/helpers/filter_helper.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,22 @@ def nice_labels
2626
}
2727
end
2828

29-
def remove_filter(query, filter)
29+
def remove_filter(query, filter, term)
3030
new_query = query.deep_dup
3131
new_query[:page] = 1
32-
new_query.delete filter.to_sym
32+
33+
if new_query[filter].length > 1
34+
new_query[filter].delete(term) # If more than one term is filtered, we only delete the selected term
35+
else
36+
new_query.delete(filter) # If only one term is filtered, delete the entire filter from the query
37+
end
38+
3339
new_query
3440
end
41+
42+
def filter_applied?(terms, term)
43+
return if terms.blank?
44+
45+
terms.include?(term)
46+
end
3547
end

app/helpers/results_helper.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module ResultsHelper
2+
def results_summary(hits)
3+
hits.to_i >= 10_000 ? '10,000+ items' : "#{number_with_delimiter(hits)} items"
4+
end
5+
end

app/views/search/_filter.html.erb

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
<% return if values.empty? %>
22

33
<div class="category">
4-
<h3><%= nice_labels[category] || category %></h3>
5-
<ul class="category-terms list-unbulleted">
6-
<% values.each do |term| %>
7-
<li class="term">
8-
<a href="<%= results_path(add_filter(@enhanced_query, category, term['key'])) %>" class="<%= "applied" if @enhanced_query[category.to_sym] == term['key'] %>">
9-
<span class="name"><%= term['key'] %></span>
10-
<span class="count"><%= term['docCount'] %> <span class="sr">records</span></span>
11-
</a>
12-
</li>
13-
<% end %>
14-
</ul>
15-
<% if @enhanced_query[category.to_sym].present? %>
16-
<div><%= link_to "Show all #{nice_labels[category]&.downcase || category}", results_path(remove_filter(@enhanced_query, category)) %>
17-
</div>
18-
<% end %>
4+
<button class="filter-label <%= 'expanded' if @enhanced_query[category.to_sym].present? || first == true %>"
5+
onclick="toggleFilter(this)"><%= nice_labels[category] || category %></button>
6+
<div class="filter-options">
7+
<ul class="category-terms list-unbulleted">
8+
<% values.each do |term| %>
9+
<li class="term">
10+
<% if filter_applied?(@enhanced_query[category.to_sym], term['key']) %>
11+
<a href="<%= results_path(remove_filter(@enhanced_query, category.to_sym, term['key'])) %>" class="applied">
12+
<span class="sr">Remove applied filter?</span>
13+
<% else %>
14+
<a href="<%= results_path(add_filter(@enhanced_query, category.to_sym, term['key'])) %>">
15+
<% end %>
16+
<span class="name"><%= term['key'] %></span>
17+
<span class="count"><%= term['docCount'] %> <span class="sr">records</span></span>
18+
</a>
19+
</li>
20+
<% end %>
21+
</ul>
22+
</div>
1923
</div>
24+
25+
<script>
26+
function toggleFilter(e) {
27+
e.parentNode.getElementsByClassName("filter-label")[0].classList.toggle("expanded");
28+
}
29+
</script>

app/views/search/_filter_empty.html.erb

Lines changed: 0 additions & 1 deletion
This file was deleted.

app/views/search/results.html.erb

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@
4949
<%= render(partial: 'shared/error', collection: @errors) %>
5050

5151
<div class="<%= 'layout-1q3q' if @filters.present? %> layout-band top-space">
52+
<% if @filters.present? %>
53+
<aside class="col1q filter-container">
54+
<div id="filters">
55+
<h2 class="hd-3">Filter your results</h2>
56+
<h3 class="hd-4"><em><%= results_summary(@pagination[:hits]) %></em></h3>
57+
<% @filters&.each_with_index do |(category, values), index| %>
58+
<% if index == 0 %>
59+
<%= render(partial: 'search/filter', locals: {category: category, values: values, first: true}) %>
60+
<% else %>
61+
<%= render(partial: 'search/filter', locals: {category: category, values: values, first: false }) %>
62+
<% end %>
63+
<% end %>
64+
</div>
65+
</aside>
66+
<% end %>
67+
5268
<div class="col3q wrap-results">
5369
<% if @results.present? %>
5470
<ul id="results" class="list-unbulleted">
@@ -61,18 +77,6 @@
6177
<% end %>
6278
</div>
6379

64-
<% if @filters.present? %>
65-
<aside class="col1q filter-container">
66-
<div id="filters">
67-
<h2>Available filters</h2>
68-
<% @filters&.each do |category, values| %>
69-
<%= render(partial: 'search/filter', locals: {category: category, values: values}) %>
70-
<% end %>
71-
</div>
72-
</aside>
73-
<% end %>
74-
</div>
75-
7680
<% if @results.present? %>
7781
<div id="pagination">
7882
<%= render partial: "pagination" %>

test/controllers/search_controller_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class SearchControllerTest < ActionDispatch::IntegrationTest
135135
get '/results?q=data'
136136
assert_response :success
137137
assert_select '#filters'
138-
assert_select '#filters .category h3', { minimum: 1 }
138+
assert_select '#filters .category .filter-label', { minimum: 1 }
139139
end
140140
end
141141

test/helpers/filter_helper_test.rb

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,66 @@ class FilterHelperTest < ActionView::TestCase
7171
original_query = {
7272
page: 1,
7373
q: 'data',
74-
contentType: 'dataset'
74+
contentType: ['dataset']
7575
}
7676
expected_query = {
7777
page: 1,
7878
q: 'data'
7979
}
80-
assert_equal expected_query, remove_filter(original_query, 'contentType')
80+
assert_equal expected_query, remove_filter(original_query, :contentType, 'dataset')
8181
end
8282

8383
test 'remove_filter will reset a page count when called' do
8484
original_query = {
8585
page: 3,
8686
q: 'data',
87-
contentType: 'dataset'
87+
contentType: ['dataset']
8888
}
8989
expected_query = {
9090
page: 1,
9191
q: 'data'
9292
}
93-
assert_equal expected_query, remove_filter(original_query, 'contentType')
93+
assert_equal expected_query, remove_filter(original_query, :contentType, 'dataset')
94+
end
95+
96+
test 'remove_filter removes only one filter parameter if multiple are applied' do
97+
original_query = {
98+
page: 3,
99+
q: 'data',
100+
contentType: ['dataset', 'microfiche', 'vinyl record']
101+
}
102+
expected_query = {
103+
page: 1,
104+
q: 'data',
105+
contentType: ['dataset', 'vinyl record']
106+
}
107+
assert_equal expected_query, remove_filter(original_query, :contentType, 'microfiche')
108+
end
109+
110+
test 'filter_applied? returns true if a filter is applied' do
111+
query = {
112+
page: 3,
113+
q: 'data',
114+
contentType: ['dataset']
115+
}
116+
assert filter_applied?(query[:contentType], 'dataset')
117+
end
118+
119+
test 'filter_applied? returns false if the filter does not include the target term' do
120+
query = {
121+
page: 3,
122+
q: 'data',
123+
contentType: ['dataset']
124+
}
125+
assert_not filter_applied?(query[:contentType], 'microfiche')
126+
end
127+
128+
# This is an unlikely state to reach, but better safe than sorry
129+
test 'filter_applied? returns false if no filter is supplied in the query' do
130+
query = {
131+
page: 3,
132+
q: 'data',
133+
}
134+
assert_not filter_applied?(query[:contentType], 'dataset')
94135
end
95136
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'test_helper'
2+
3+
class ResultsHelperTest < ActionView::TestCase
4+
include ResultsHelper
5+
6+
test 'if number of hits is equal to 10,000, results summary returns "10,000+"' do
7+
hits = 10000
8+
assert_equal '10,000+ items', results_summary(hits)
9+
end
10+
11+
test 'if number of hits is above 10,000, results summary returns "10,000+"' do
12+
hits = 10500
13+
assert_equal '10,000+ items', results_summary(hits)
14+
end
15+
16+
test 'if number of hits is below 10,000, results summary returns actual number of results' do
17+
hits = 9000
18+
assert_equal '9,000 items', results_summary(hits)
19+
end
20+
end

0 commit comments

Comments
 (0)