Skip to content

Commit a647d5f

Browse files
fix Issues/515: replace country checkbox facet with searchable dropdown to resolve pagination filtering bug (#542)
* Add separate countryFacet with selectionbox and set conditional rendering in directory default * implement advanced searchable country dropdown for MVP directory * Minor change to include only relavant changes in default.cshtml file * Fix line and spacing issue from the code - _CountryFacet.cshtml * Update the mvp-site.js changes to single document ready function * Set the background colour to matching other facets * Update the min.js version in layout.cshtml * Add updated compiled css files minified JS assets * refactor: fix duplicate event binding in CountryFacet dropdown options and update js assets * use a clean country name for the input value and render counts only in the dropdown options for consistency in ux Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * No localstorage usage - default mode country list * Changes of country search facet to the search and close button approach * Remove unused methods and styles of coutry facet * Few js code optimizations and remove unused codes * Style and UI/UX update to maintain the consistency of the UI * Use badge-secondary badge-pill for the count badge-pill implementation * Make the close button stand out making bold-red text * fix: ensure country facet dropdown option is selected on any click inside option --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent eb3b545 commit a647d5f

File tree

10 files changed

+3678
-3627
lines changed

10 files changed

+3678
-3627
lines changed

headapps/MvpSite/MvpSite.Rendering/Views/Shared/Components/Directory/Default.cshtml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using MvpSite.Rendering.Models.Directory
2+
@using MvpSite.Rendering.ViewComponents
23
@model DirectoryViewModel
34

45
<section class="mvp-fp-directory bg-white">
@@ -26,7 +27,14 @@
2627
<div class="col-sm-12 col-md-3">
2728
@foreach (FacetViewModel facet in Model.ViewFacets.OrderBy(f => f.SortOrder))
2829
{
29-
<partial name="~/Views/Shared/Components/Directory/_Facet.cshtml" model="facet" />
30+
@if (facet.Identifier == DirectoryViewComponent.CountryFacetIdentifier)
31+
{
32+
<partial name="~/Views/Shared/Components/Directory/_CountryFacet.cshtml" model="facet" />
33+
}
34+
else
35+
{
36+
<partial name="~/Views/Shared/Components/Directory/_Facet.cshtml" model="facet" />
37+
}
3038
}
3139
</div>
3240
<div class="col-sm-12 col-md-9 mb-3 ">
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
@using MvpSite.Rendering.Models.Directory
2+
@using MvpSite.Rendering.ViewComponents
3+
@model MvpSite.Rendering.Models.Directory.FacetViewModel
4+
5+
@if (Model.FacetOptions.Count > 0)
6+
{
7+
string id = "fcid_" + Model.Identifier;
8+
<div class="mvp-facet mb-4">
9+
<a data-toggle="collapse" href="#@id" role="button" aria-expanded="true" aria-controls="@id">
10+
<h3 asp-for="Name"> </h3>
11+
<span class="icon-chevron-down "></span>
12+
</a>
13+
<div id="@id" class="collapse show">
14+
<div class="form-group">
15+
<div class="searchable-select-wrapper">
16+
<div class="country-selected-display" id="country-selected-@Model.Identifier" style="display:none">
17+
<span class="country-selected-label"></span>
18+
<span class="badge badge-secondary badge-pill"></span>
19+
<button type="button"
20+
class="country-clear-btn"
21+
id="country-clear-@Model.Identifier"
22+
title="Clear selection">
23+
×
24+
</button>
25+
</div>
26+
<input type="text"
27+
id="country-search-@Model.Identifier"
28+
class="form-control country-search"
29+
placeholder="Search countries..."
30+
autocomplete="off" />
31+
<div class="searchable-dropdown" id="country-dropdown-@Model.Identifier">
32+
<div class="dropdown-option @(Model.FacetOptions.All(o => !o.IsActive) ? "selected" : "")"
33+
data-value=""
34+
data-display="All Countries">
35+
All Countries
36+
</div>
37+
@foreach (FacetOption option in Model.FacetOptions.Where(o => o.Count > 0).OrderBy(o => o.Display))
38+
{
39+
<div class="dropdown-option @(option.IsActive ? "selected" : "")"
40+
data-value="@option.Identifier"
41+
data-display="@option.Display">
42+
<span class="country-label">@option.Display</span>
43+
<span class="badge badge-secondary badge-pill">@option.Count</span>
44+
</div>
45+
}
46+
</div>
47+
<input type="hidden"
48+
name="@(DirectoryViewComponent.FacetQuerystringPrefix + Model.Identifier)"
49+
id="country-hidden-@Model.Identifier"
50+
value="@(Model.FacetOptions.FirstOrDefault(o => o.IsActive)?.Identifier ?? "")" />
51+
</div>
52+
</div>
53+
</div>
54+
</div>
55+
}

headapps/MvpSite/MvpSite.Rendering/Views/Shared/_Layout.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,6 @@
6969
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
7070
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js"></script>
7171
<script src="https://www.w3.org/TR/wai-aria-practices-1.1/examples/accordion/js/accordion.js"></script>
72-
<script src="/js/mvp-site.es5.min.js?v=1.15"></script>
72+
<script src="/js/mvp-site.es5.min.js?v=1.16"></script>
7373
</body>
7474
</html>

headapps/MvpSite/MvpSite.Rendering/wwwroot/js/mvp-site.es5.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,190 @@ $(document).ready(function () {
147147
});
148148
}
149149

150+
// Initialize country facets
151+
var countryFacets = document.querySelectorAll('[id^="country-search-"]');
152+
for (var i = 0; i < countryFacets.length; i++) {
153+
var input = countryFacets[i];
154+
var identifier = input.id.replace('country-search-', '');
155+
new CountryFacet(identifier);
156+
}
157+
150158
var $mvpmentordata = $(".mvp-fs-mvpmentordata");
151159
if ($mvpmentordata.length > 0) {
152160
setupMentorCheckboxBehavior();
153161
}
154162
});
155163

164+
/**
165+
* Country Facet Searchable Dropdown Module
166+
*/
167+
function CountryFacet(identifier) {
168+
this.searchInput = document.getElementById('country-search-' + identifier);
169+
this.dropdown = document.getElementById('country-dropdown-' + identifier);
170+
this.hiddenInput = document.getElementById('country-hidden-' + identifier);
171+
this.clearButton = document.getElementById('country-clear-' + identifier);
172+
173+
if (!this.searchInput || !this.dropdown || !this.hiddenInput || !this.clearButton) {
174+
console.warn('CountryFacet: Required elements not found for identifier: ' + identifier);
175+
return;
176+
}
177+
178+
this.isLocked = false;
179+
this.identifier = identifier;
180+
this.init();
181+
}
182+
183+
CountryFacet.prototype.init = function () {
184+
this.setInitialValue();
185+
this.bindEvents();
186+
};
187+
188+
CountryFacet.prototype.getDisplayValue = function (option) {
189+
var value = option.getAttribute('data-value');
190+
if (value === '') return '';
191+
192+
var displayText = option.getAttribute('data-display');
193+
var countMatch = option.textContent.trim().match(/\((\d+)\)$/);
194+
195+
return countMatch ? displayText + ' (' + countMatch[1] + ')' : displayText;
196+
};
197+
198+
CountryFacet.prototype.selectCountry = function (option) {
199+
var selected = this.dropdown.querySelector('.dropdown-option.selected');
200+
if (selected) selected.classList.remove('selected');
201+
option.classList.add('selected');
202+
var value = option.getAttribute('data-value');
203+
this.hiddenInput.value = value;
204+
var label = option.querySelector('.country-label') ? option.querySelector('.country-label').textContent : option.getAttribute('data-display');
205+
var count = option.querySelector('.badge') ? option.querySelector('.badge').textContent : '';
206+
if (value !== '') {
207+
this.showSelectedDisplay(label, count);
208+
this.lockInput();
209+
} else {
210+
this.hideSelectedDisplay();
211+
this.unlockInput();
212+
}
213+
this.hiddenInput.form.submit();
214+
};
215+
216+
CountryFacet.prototype.showDropdown = function () {
217+
this.dropdown.style.display = 'block';
218+
this.showAllOptions();
219+
};
220+
221+
CountryFacet.prototype.hideDropdown = function () {
222+
this.dropdown.style.display = 'none';
223+
};
224+
225+
CountryFacet.prototype.lockInput = function () {
226+
this.isLocked = true;
227+
this.searchInput.classList.add('locked');
228+
this.searchInput.readOnly = true;
229+
this.clearButton.classList.add('visible');
230+
};
231+
232+
CountryFacet.prototype.unlockInput = function () {
233+
this.isLocked = false;
234+
this.searchInput.classList.remove('locked');
235+
this.searchInput.readOnly = false;
236+
this.clearButton.classList.remove('visible');
237+
};
238+
239+
CountryFacet.prototype.clearSelection = function () {
240+
var allCountriesOption = this.dropdown.querySelector('.dropdown-option[data-value=""]');
241+
if (allCountriesOption) {
242+
this.selectCountry(allCountriesOption);
243+
}
244+
};
245+
246+
CountryFacet.prototype.showAllOptions = function () {
247+
var options = this.dropdown.querySelectorAll('.dropdown-option');
248+
for (var i = 0; i < options.length; i++) {
249+
options[i].classList.remove('hidden');
250+
}
251+
};
252+
253+
CountryFacet.prototype.filterOptions = function (searchTerm) {
254+
var options = this.dropdown.querySelectorAll('.dropdown-option');
255+
var lowerSearchTerm = searchTerm.toLowerCase();
256+
257+
for (var i = 0; i < options.length; i++) {
258+
var matches = searchTerm === '' || options[i].textContent.toLowerCase().indexOf(lowerSearchTerm) !== -1;
259+
options[i].classList.toggle('hidden', !matches);
260+
}
261+
};
262+
263+
CountryFacet.prototype.setInitialValue = function () {
264+
var selectedOption = this.dropdown.querySelector('.dropdown-option.selected');
265+
if (selectedOption && selectedOption.getAttribute('data-value') !== '') {
266+
var label = selectedOption.querySelector('.country-label') ? selectedOption.querySelector('.country-label').textContent : selectedOption.getAttribute('data-display');
267+
var count = selectedOption.querySelector('.badge') ? selectedOption.querySelector('.badge').textContent : '';
268+
this.showSelectedDisplay(label, count);
269+
this.lockInput();
270+
} else {
271+
this.hideSelectedDisplay();
272+
this.unlockInput();
273+
}
274+
};
275+
276+
CountryFacet.prototype.bindEvents = function () {
277+
var self = this;
278+
279+
this.searchInput.addEventListener('focus', function () {
280+
if (!self.isLocked) {
281+
self.showDropdown();
282+
}
283+
});
284+
285+
this.searchInput.addEventListener('input', function (e) {
286+
if (!self.isLocked) {
287+
self.showDropdown();
288+
self.filterOptions(e.target.value);
289+
}
290+
});
291+
292+
this.clearButton.addEventListener('click', function () {
293+
self.clearSelection();
294+
});
295+
296+
document.addEventListener('click', function (e) {
297+
var isClickInside = self.searchInput.contains(e.target) || self.dropdown.contains(e.target) || self.clearButton.contains(e.target);
298+
if (!isClickInside) {
299+
self.hideDropdown();
300+
}
301+
});
302+
303+
this.dropdown.addEventListener('click', function (e) {
304+
var option = e.target.closest('.dropdown-option');
305+
if (option) {
306+
self.selectCountry(option);
307+
}
308+
});
309+
};
310+
311+
CountryFacet.prototype.showSelectedDisplay = function (label, count) {
312+
this.searchInput.style.display = 'none';
313+
this.dropdown.style.display = 'none';
314+
var selectedDisplay = document.getElementById('country-selected-' + this.identifier);
315+
if (selectedDisplay) {
316+
selectedDisplay.style.display = 'flex';
317+
selectedDisplay.querySelector('.country-selected-label').textContent = label;
318+
var countSpan = selectedDisplay.querySelector('.badge');
319+
if (count) {
320+
countSpan.textContent = count;
321+
countSpan.style.display = 'inline-block';
322+
} else {
323+
countSpan.textContent = '';
324+
countSpan.style.display = 'none';
325+
}
326+
}
327+
};
328+
329+
CountryFacet.prototype.hideSelectedDisplay = function () {
330+
var selectedDisplay = document.getElementById('country-selected-' + this.identifier);
331+
if (selectedDisplay) {
332+
selectedDisplay.style.display = 'none';
333+
}
334+
this.searchInput.style.display = '';
335+
};
336+

0 commit comments

Comments
 (0)