Skip to content

Commit e112d74

Browse files
committed
Add search to library
1 parent 8f6ec29 commit e112d74

3 files changed

Lines changed: 59 additions & 28 deletions

File tree

components/rsptx/templates/book/index.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ <h2>Runestone Academy Library of Books</h2>
2525
the <a href="/admin/auth/my_courses">Change Course</a> page.
2626
</p>
2727

28-
<!--
2928
<div class="searchbar">
3029
<input
3130
type="text"
3231
id="searchbar"
3332
onkeyup="search_book()"
34-
placeholder="Search by keyword..."
33+
placeholder="Search by title, author, description, or book name..."
34+
aria-label="Search the library"
3535
/>
3636
</div>
37-
-->
37+
<p id="search_no_results" style="display: none">
38+
No books match your search.
39+
</p>
3840
<hr />
3941

4042
{% for section in sections: %}
@@ -45,7 +47,10 @@ <h2>Runestone Academy Library of Books</h2>
4547

4648
{% for book in book_list: %}
4749
{% if book['shelf_section'] == section: %}
48-
<div class="library_entry">
50+
<div
51+
class="library_entry"
52+
data-search="{{ (book['title'] ~ ' ' ~ (book['authors'] or '') ~ ' ' ~ (book['description'] or '') ~ ' ' ~ book['basecourse']) | lower }}"
53+
>
4954
<div class="book_title">
5055
<a
5156
class="link1"
Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,47 @@
1-
//This is the JavaScript function of the search bar of the library
1+
// This is the JavaScript function of the search bar of the library.
2+
// A book matches when every word typed appears somewhere in the book's
3+
// title, author(s), description, or base course name (the `data-search`
4+
// attribute built in the template holds all four fields, lower-cased).
25

36
function search_book() {
4-
let input = document.getElementById("searchbar").value;
5-
input = input.toLowerCase();
6-
let x = document.getElementsByClassName("book_title");
7-
let y = document.getElementsByClassName("book_descript");
7+
let input = document.getElementById("searchbar").value.toLowerCase();
8+
let words = input.split(/\s+/).filter((w) => w.length > 0);
89

9-
for (i = 0; i < x.length; i++) {
10-
if (
11-
x[i].innerHTML.toLowerCase().includes(input) ||
12-
y[i].innerHTML.toLowerCase().includes(input)
13-
) {
14-
x[i].parentElement.style.display = "list-item";
15-
x[i].parentElement.previousElementSibling.style.display =
16-
"list-item";
17-
y[i].parentElement.style.display = "list-item";
18-
y[i].parentElement.previousElementSibling.style.display =
19-
"list-item";
20-
} else {
21-
x[i].parentElement.style.display = "none";
22-
y[i].parentElement.style.display = "none";
23-
}
24-
}
10+
let sections = document.querySelectorAll("details");
11+
let totalMatches = 0;
2512

26-
// remove section headings when searching - restore when the search box is empty
27-
let sections = document.querySelectorAll(".sectionName");
2813
sections.forEach((sec) => {
29-
sec.style.display = input ? "none" : "list-item";
14+
let entries = sec.querySelectorAll(".library_entry");
15+
let sectionMatches = 0;
16+
17+
entries.forEach((entry) => {
18+
let haystack = entry.getAttribute("data-search") || "";
19+
// Match only when every word is found somewhere in the fields.
20+
let matches = words.every((w) => haystack.includes(w));
21+
entry.style.display = matches ? "" : "none";
22+
if (matches) {
23+
sectionMatches++;
24+
}
25+
});
26+
totalMatches += sectionMatches;
27+
28+
if (words.length === 0) {
29+
// Empty box: restore the original collapsed sections.
30+
sec.style.display = "";
31+
sec.removeAttribute("open");
32+
} else if (sectionMatches > 0) {
33+
// Open any section that has a match, even if it was closed.
34+
sec.style.display = "";
35+
sec.setAttribute("open", "");
36+
} else {
37+
// Hide sections with no matches while searching.
38+
sec.style.display = "none";
39+
}
3040
});
41+
42+
let noResults = document.getElementById("search_no_results");
43+
if (noResults) {
44+
noResults.style.display =
45+
words.length > 0 && totalMatches === 0 ? "block" : "none";
46+
}
3147
}

components/rsptx/templates/staticAssets/index.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ input {
8484
outline-style: none;
8585
}
8686

87+
/* The .searchbar box (border, icon, padding) is on the wrapping div; make the
88+
input fill it so the placeholder isn't clipped at the input's default width. */
89+
.searchbar input {
90+
width: 100%;
91+
box-sizing: border-box;
92+
font-size: inherit;
93+
border: none;
94+
background: transparent;
95+
}
96+
8797
.library_entry {
8898
margin-bottom: 5pt;
8999
}

0 commit comments

Comments
 (0)