Skip to content

Commit

Permalink
Rework FAQ page with caching and fuzzing finder searchbar + arrow dro…
Browse files Browse the repository at this point in the history
…pdown auto toggle when other items clicked (#813)
  • Loading branch information
navidemad committed May 19, 2021
1 parent 227a5cf commit 4fd37ce
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 22 deletions.
29 changes: 29 additions & 0 deletions app/assets/stylesheets/pages/_faq.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
#faq {
#fuzzy-search {
&.search-done {
h2 {
display: none !important;
}
}
}

h3 button {
padding-left: 0;
width: 100%;
text-align: left;
position: relative;

&:after {
position: absolute;
right: 0px;
top: 50%;
margin-top: -10px;
font-family: "Font Awesome 5 Free";
font-weight: 900;
font-size: 15px;
font-style: normal;
font-variant: normal;
display: inline-block;
text-rendering: auto;
color: $purple700;
content: "\f106";
}

&.collapsed:after {
content: "\f107";
}
}

.card-body p:last-child {
Expand Down
12 changes: 10 additions & 2 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,19 @@ def algorithme
end

def faq
@faq_items = FaqItem.where(area: "main")
@faq_items = Rails.cache.fetch("faq_items_main", expires_in: 2.hours) { FaqItem.where(area: "main") }
respond_to do |format|
format.html
format.json { render json: @faq_items.to_json }
end
end

def faq_pro
@faq_items = FaqItem.where(area: "pro")
@faq_items = Rails.cache.fetch("faq_items_pro", expires_in: 2.hours) { FaqItem.where(area: "pro") }
respond_to do |format|
format.html
format.json { render json: @faq_items.to_json }
end
end

def robots
Expand Down
5 changes: 5 additions & 0 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ document.addEventListener("turbolinks:load", () => {
smoothScrollAnchor();
toggleMobileMatchInformation();
$('[data-toggle="tooltip"]').tooltip();

// webpack will load this JS async
if (document.getElementById("fuzzy-search")) {
import("../plugins/fuzzy_search");
}
});
95 changes: 95 additions & 0 deletions app/javascript/plugins/fuzzy_search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Fuse from "fuse.js/dist/fuse.basic.esm";

const engine = document.getElementById("fuzzy-search");

if (engine) {
let source = engine.getAttribute("data-fuzzy-search-source");
let domInput = document.querySelector(
engine.getAttribute("data-fuzzy-search-input")
);
let domNoResults = document.querySelector(
engine.getAttribute("data-fuzzy-search-none")
);
let domItems = document.querySelectorAll(
engine.getAttribute("data-fuzzy-search-item")
);
let domItemTitleSelector = engine.getAttribute(
"data-fuzzy-search-item-title"
);

if (!domInput) {
console.error("Aucun champ texte de recherche de trouvé");
} else if (!domItems) {
console.error("Aucun jeu de donnée pre-existant");
} else {
const trustScoreFactor = 0.45;
const fuseOptions = {
isCaseSensitive: false,
includeScore: true,
shouldSort: true,
includeMatches: false,
findAllMatches: false,
minMatchCharLength: 3,
threshold: trustScoreFactor,
useExtendedSearch: false,
ignoreLocation: true,
ignoreFieldNorm: false,
keys: ["category", "title", "body_md_erb"],
};

fetch(source)
.then((response) => response.json())
.then((dataset) => {
const fuse = new Fuse(dataset, fuseOptions);
let domItemsCount = domItems.length;
domInput.addEventListener(
"keyup",
function (e) {
const searchTerm = this.value;
if (searchTerm.length < 3) {
engine.classList.remove("search-done");
domNoResults.classList.add("d-none");
for (var i = 0; i < domItemsCount; i++)
domItems[i].classList.remove("d-none");
} else {
engine.classList.add("search-done");
const matches = fuse.search(searchTerm);
console.clear();
console.table(matches);
const matchesFiltered = matches.filter(
(match) => match.score <= trustScoreFactor
);
const matchesTitles = matchesFiltered.map((r) => r.item["title"]);
if (matchesTitles.length > 0) {
domNoResults.classList.add("d-none");
for (var i = 0; i < domItemsCount; i++) {
const domItemTitle = domItems[i]
.querySelector(domItemTitleSelector)
.textContent.replace(/^\s+|\s+$/g, "");
if (
searchTerm.length == 0 ||
matchesTitles.indexOf(domItemTitle) >= 0
) {
domItems[i].classList.remove("d-none");
domItems[i].style.order = matchesTitles.indexOf(
domItemTitle
);
} else {
domItems[i].classList.add("d-none");
domItems[i].style.order = "inherit";
}
}
} else {
domNoResults.classList.remove("d-none");
for (var i = 0; i < domItemsCount; i++) {
domItems[i].classList.add("d-none");
}
}
}
},
false
);
})
.catch(console.error);
}
}
9 changes: 5 additions & 4 deletions app/views/pages/_faq_entry.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="card">
<div class="card-header">
<div class="card mt-1 faq-item">
<div class="card-header py-1">
<h3 class="mb-0">
<button class="btn btn-link"
<button class="btn btn-link collapsed faq-item--title"
data-toggle="collapse"
data-target="#<%= faq_item.id %>"
aria-expanded="false"
Expand All @@ -13,7 +13,8 @@

<div id="<%= faq_item.id %>"
class="collapse"
aria-labelledby="<%= faq_item.id %>">
aria-labelledby="<%= faq_item.id %>"
data-parent="#accordion">
<div class="card-body">
<%= faq_item.body %>
</div>
Expand Down
18 changes: 10 additions & 8 deletions app/views/pages/faq.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
<%= link_to "foire aux questions pour les professionnels de santé", partenaires_faq_path %>.
</p>

<div id="accordion">
<% current_category = nil %>
<% @faq_items.each do |faq_item| %>
<% if current_category != faq_item.category %>
<h2 class="mt-5 mb-3"><%= current_category = faq_item.category %></h2>
<div id="fuzzy-search" data-fuzzy-search-source="<%= faq_path(format: :json) %>" data-fuzzy-search-input="#search" data-fuzzy-search-none="#no-results" data-fuzzy-search-item-category=".faq-category" data-fuzzy-search-item=".faq-item" data-fuzzy-search-item-title=".faq-item--title">
<%= search_field_tag :search, params[:search], placeholder: "Saisissez un mot-clé pour trouver votre réponse", class: 'form-control string required', autocomplete: "off" %>
<div id="no-results" class="d-none alert alert-warning mt-2">Nous n’avons pas trouvé de réponse avec ces mots clefs, merci de reformuler votre demande.</div>
<div id="accordion" class="d-flex flex-column">
<% @faq_items.group_by(&:category).each do |category, faq_items| %>
<h2 class="mt-4 mb-3 h4"><%= category %></h2>
<% faq_items.each do |faq_item| %>
<%= render "faq_entry", faq_item: faq_item %>
<% end %>
<% end %>
<%= render "faq_entry", faq_item: faq_item %>
<% end %>
</div>
</div>
</div>
18 changes: 10 additions & 8 deletions app/views/pages/faq_pro.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
<%= link_to "foire aux questions pour les volontaires à la vaccination", faq_path %>.
</p>

<div id="accordion">
<% current_category = nil %>
<% @faq_items.each do |faq_item| %>
<% if current_category != faq_item.category %>
<h2 class="mt-5 mb-3"><%= current_category = faq_item.category %></h2>
<div id="fuzzy-search" data-fuzzy-search-source="<%= partenaires_faq_path(format: :json) %>" data-fuzzy-search-input="#search" data-fuzzy-search-none="#no-results" data-fuzzy-search-item-category=".faq-category" data-fuzzy-search-item=".faq-item" data-fuzzy-search-item-title=".faq-item--title">
<%= search_field_tag :search, params[:search], placeholder: "Saisissez un mot-clé pour trouver votre réponse", class: 'form-control string required', autocomplete: "off" %>
<div id="no-results" class="d-none alert alert-warning mt-2">Nous n’avons pas trouvé de réponse avec ces mots clefs, merci de reformuler votre demande.</div>
<div id="accordion" class="d-flex flex-column">
<% @faq_items.group_by(&:category).each do |category, faq_items| %>
<h2 class="mt-4 mb-3 h4"><%= category %></h2>
<% faq_items.each do |faq_item| %>
<%= render "faq_entry", faq_item: faq_item %>
<% end %>
<% end %>
<%= render "faq_entry", faq_item: faq_item %>
<% end %>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"bootstrap-select": "^1.13.18",
"dayjs": "^1.10.4",
"formik": "^2.2.6",
"fuse.js": "^6.4.6",
"is-svg": "^4.2.2",
"jquery": "^3.6.0",
"leaflet": "^1.7.1",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4fd37ce

Please sign in to comment.