Skip to content

Commit

Permalink
💄 [#2741] Added multiselect listbox mobile structure
Browse files Browse the repository at this point in the history
  • Loading branch information
jiromaykin committed Oct 22, 2024
1 parent 16de4dc commit 90b41e6
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
{% if text_icon %}
<span class="button__text-wrapper">
{% icon icon=text_icon outlined=icon_outlined %}
{% if not hide_text %}{{ text }}{% endif %}
{% if not hide_text %}<span class="button__inner-text">{{ text }}</span>{% endif %}
</span>
{% else %}
{% if not hide_text %}{{ text }}{% endif %}
{% if not hide_text %}<span class="button__inner-text">{{ text }}</span>{% endif %}
{% endif %}
</button>
{% endif %}
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
{% load i18n form_tags %}
{% load i18n form_tags button_tags %}

{# Wrapper for multiple filters #}
<div class="filter-bar" id="filterBar">
<form class="form" method="{{ method }}"
{% if no_action %}action=""{% else %}action="{% firstof form_action request.path %}"{% endif %}
{% if id %}id="{{ id }}"{% endif %}>
{# Note: each element inside the form is a flex column #}
{{ contents }}
</form>
<div class="filter-bar__backdrop" id="filterBarBackdrop">
{# Wrapper for multiple filters #}
<div class="filter-bar" id="filterBar">
<div class="filter-bar__mobile-controls">
{% button icon="close" text=_("Sluiten") hide_text=True icon_outlined=True transparent=True extra_classes="show-controls" %}
<div class="form__reset--mobile form__actions--fullwidth form__actions--reset">
<button class="button button--primary button--transparent" type="button" name="" value="" title="Wis alle filters" aria-label="Wis alle filters" id="resetAllFilters">
Wis alle filters
</button>
</div>
</div>
<div class="filter-bar__mobile-button">
<p class="utrecht-paragraph filter-bar__heading">Filters</p>
{% button icon="filter_alt" text=_("Filters") icon_outlined=True transparent=True extra_classes="show-modal" %}
<p class="utrecht-paragraph filter-bar__status-text">Status</p>
</div>
<form class="form filter-bar__form" method="{{ method }}"
{% if no_action %}action="" {% else %}action="{% firstof form_action request.path %}" {% endif %}
{% if id %}id="{{ id }}" {% endif %}>
{# Note: each element inside the form is a flex column #}
{{ contents }}

{# Mobile submit button updates on select #}
<div class="form__reload--mobile form__actions form__actions--fullwidth" id="filterFormActions">
<button class="button button--primary" type="submit" title="{% trans 'Toon resultaten' %}" aria-label="{% trans 'Toon resultaten' %}" id="filterCases">
{% trans 'Toon' %}<span class="filter-bar__frequency-sum" id="frequencySum">0</span><span id="resultText">{% trans 'resultaten' %}</span>
</button>
</div>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
{% load i18n l10n form_tags icon_tags button_tags %}

<div class="filter-bar__multiselect-listbox multiselect-listbox" id="selectDropdownWrapper">
<button id="selectButton" type="button" class="button button__select" aria-haspopup="listbox" aria-expanded="false" aria-live="polite">
{% trans 'Status' %}:
{% icon icon="expand_more" icon_position="after" icon_outlined=True %}
</button>
<div id="listboxDropdown" class="multiselect-listbox__content" role="listbox" aria-labelledby="selectButton">
<div class="multiselect-listbox__scroll" role="presentation">
{% for status, frequency in statusfrequencies %}
<div class="checkbox" role="option">
<input type="checkbox" name="status" value="{{ status }}" id="id_status_{{ forloop.counter }}" class="checkbox__input">
<label class="checkbox__label" for="id_status_{{ forloop.counter }}">
<span class="ellipsis">{{ status }} </span><span class="frequency-counter">({{ frequency }})</span>
</label>
</div>
{% endfor %}
<div class="multiselect-listbox__popup">
<button id="selectButton" type="button" class="button button__select" aria-haspopup="listbox" aria-expanded="false" aria-live="polite">
{% trans 'Status' %}:
{% icon icon="expand_more" icon_position="after" icon_outlined=True %}
</button>
<div id="listboxDropdown" class="multiselect-listbox__content" role="listbox" aria-labelledby="selectButton">
<div class="multiselect-listbox__scroll" role="presentation">
{% for status, frequency in statusfrequencies %}
<div class="checkbox" role="option">
<input type="checkbox" name="status" value="{{ status }}" id="id_status_{{ forloop.counter }}" class="checkbox__input">
<label class="checkbox__label" for="id_status_{{ forloop.counter }}">
<span class="ellipsis">{{ status }} </span><span class="frequency-counter">({{ frequency }})</span>
</label>
</div>
{% endfor %}
</div>
{# Submit button updates on select #}
<div class="form__actions form__actions--fullwidth" id="multiselectFormActions">
<button class="button button--primary" type="submit" title="{% trans 'Toon resultaten' %}" aria-label="{% trans 'Toon resultaten' %}" id="filterCases">
{% trans 'Toon' %}<span class="filter-bar__frequency-sum" id="frequencySum">0</span><span id="resultText">{% trans 'resultaten' %}</span>
</button>
</div>
</div>
{# Submit button appears on select #}
<div class="form__actions form__actions--fullwidth" id="filterFormActions">
<button class="button button--primary" type="submit" title="{% trans 'Toon resultaten' %}" aria-label="{% trans 'Toon resultaten' %}" id="filterCases">
{% trans 'Toon' %}<span class="filter-bar__frequency-sum" id="frequencySum">0</span><span id="resultText">{% trans 'resultaten' %}</span>
</button>
<div class="form__reset--desktop form__actions--fullwidth form__actions--reset">
{% button bordered=False text=_("Wis alle filters") id="resetMultiSelectFilters" type="button" transparent=True primary=True %}
</div>
</div>
<div class="form__actions form__actions--fullwidth form__actions--reset">
{% button bordered=False text=_("Wis alle filters") id="resetFilters" type="button" transparent=True primary=True %}
</div>
</div>
102 changes: 102 additions & 0 deletions src/open_inwoner/js/components/FilterBar/filterbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
export class FilterBar {
static selector = '.filter-bar'

constructor(node) {
this.node = node
this.filterPopup = node.querySelector('.show-modal')
this.filterButton = node.querySelector('#selectButton')
this.backdrop = document.getElementById('filterBarBackdrop')
this.closeButton = node.querySelector('.show-controls')

// Check if elements are found
if (!this.filterPopup) {
console.error('Filter popup button not found!')
return
}

if (!this.filterButton) {
console.error('Select button not found!')
return
}

// Event listeners
this.filterPopup.addEventListener(
'click',
this.toggleOpenFilterPopup.bind(this)
)
this.closeButton.addEventListener(
'click',
this.closeFilterPopupDirect.bind(this) // Added a specific handler for direct close button click
)
document.addEventListener('click', this.closeFilterPopup.bind(this), false)
document.addEventListener(
'keydown',
this.closeFilterPopup.bind(this),
false
)
}

toggleOpenFilterPopup(event) {
event.preventDefault()

// Add 'show' class to the backdrop to make it visible
this.backdrop.classList.add('show')

// Toggle mobile filter class
setTimeout(() => {
this.node.classList.toggle('filter-bar--mobile')
const isExpanded =
this.filterPopup.getAttribute('aria-expanded') === 'true'
this.filterPopup.setAttribute('aria-expanded', !isExpanded)
}, 5)
}

closeFilterPopupDirect(event) {
// Remove 'show' class from the backdrop to hide it
this.backdrop.classList.remove('show')

// Remove mobile class and reset aria-expanded
this.node.classList.remove('filter-bar--mobile')
this.filterPopup.setAttribute('aria-expanded', 'false')
}

closeFilterPopup(event) {
// Close on clicking outside or pressing Escape
if (
(event.type === 'keydown' && event.key === 'Escape') ||
(event.type === 'click' &&
!this.node.contains(event.target) &&
!this.filterPopup.contains(event.target) &&
!this.backdrop.contains(event.target))
) {
// Remove 'show' class from the backdrop to hide it
this.backdrop.classList.remove('show')

// Remove mobile class and reset aria-expanded
this.node.classList.remove('filter-bar--mobile')
this.filterPopup.setAttribute('aria-expanded', 'false')
}
}
}

// Reinitialize FilterBar after HTMX swap
htmx.on('htmx:afterSwap', function (e) {
if (e.detail && e.detail.target.id === 'cases-content') {
const filterBars = document.querySelectorAll(FilterBar.selector)
if (filterBars.length === 0) {
console.error('No filter bars found on the page after swap.')
} else {
filterBars.forEach((filterbar) => new FilterBar(filterbar))
}
}
})

// Initialize FilterBar on DOM load for the initial page load
document.addEventListener('DOMContentLoaded', () => {
const filterBars = document.querySelectorAll(FilterBar.selector)
if (filterBars.length === 0) {
console.error('No filter bars found on the page.')
} else {
filterBars.forEach((filterbar) => new FilterBar(filterbar))
}
})
1 change: 1 addition & 0 deletions src/open_inwoner/js/components/FilterBar/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import './filterbar'
import './multiselect_listbox_checkbox'
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ function initFilterBar() {
)
let sum = 0
let selectedFilters = []
let anyChecked = false

checkboxes.forEach((checkbox) => {
if (checkbox.checked) {
anyChecked = true // Mark that we have at least one checkbox checked
const label = checkbox.nextElementSibling
selectedFilters.push(label.textContent.trim())
const frequencyCounter = label.querySelector('.frequency-counter')
Expand All @@ -52,6 +54,7 @@ function initFilterBar() {
let closeIcon = document.createElement('span')
closeIcon.classList.add('material-icons', 'close-icon')
closeIcon.setAttribute('aria-hidden', 'true')
closeIcon.setAttribute('tabindex', '0') // Adding tabindex for keyboard focus
closeIcon.textContent = 'close'

// Add text and icons based on selected filters
Expand All @@ -76,12 +79,28 @@ function initFilterBar() {
selectButton.classList.add('active')
}

closeIcon.addEventListener('click', function (event) {
event.stopPropagation()
const handleClose = function () {
checkboxes.forEach((checkbox) => {
checkbox.checked = false
})
calculateAndDisplayCheckedSum() // Recalculate and update the button and sum
calculateAndDisplayCheckedSum() // Recalculate and update the button and sum, even after refresh
const filterBarForm = document.querySelector('#filterBar .form')
if (filterBarForm) {
filterBarForm.submit()
}
}

closeIcon.addEventListener('click', function (event) {
event.stopPropagation()
handleClose()
})

// Add accessibility functionality for close icon
closeIcon.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
handleClose()
}
})

selectButton.setAttribute('aria-live', 'polite')
Expand All @@ -96,6 +115,18 @@ function initFilterBar() {
if (resultTextElement) {
resultTextElement.textContent = sum === 1 ? 'resultaat' : 'resultaten'
}

// Handle visibility of resetMultiSelectFilters
const resetMultiSelectFilters = document.getElementById(
'resetMultiSelectFilters'
)
if (resetMultiSelectFilters) {
if (anyChecked) {
resetMultiSelectFilters.classList.remove('hide') // Show the button
} else {
resetMultiSelectFilters.classList.add('hide') // Hide the button
}
}
}

const initSelectBehavior = function () {
Expand Down Expand Up @@ -177,10 +208,35 @@ function initFilterBar() {
}
})

const resetFilters = document.getElementById('resetFilters')
if (resetFilters) {
resetFilters.addEventListener('click', function (e) {
const resetMultiSelectFilters = document.getElementById(
'resetMultiSelectFilters'
)
const resetAllFilters = document.getElementById('resetAllFilters')

if (resetMultiSelectFilters) {
resetMultiSelectFilters.addEventListener('click', function (e) {
e.preventDefault()

const checkboxes = document.querySelectorAll(
'.filter-bar .checkbox__input'
)
checkboxes.forEach((checkbox) => {
checkbox.checked = false
})

calculateAndDisplayCheckedSum()

const filterBarForm = document.querySelector('#filterBar .form')
if (filterBarForm) {
filterBarForm.submit()
}
})
}

if (resetAllFilters) {
resetAllFilters.addEventListener('click', function (e) {
e.preventDefault()

const checkboxes = document.querySelectorAll(
'.filter-bar .checkbox__input'
)
Expand Down Expand Up @@ -224,9 +280,6 @@ document.body.addEventListener('htmx:afterSwap', function () {

document.addEventListener('click', function (e) {
if (e.target && e.target.classList.contains('pagination__link')) {
scrollToTopOfWindow()
setTimeout(function () {
initFilterBar() // Reinitialize filter bar after swap
}, 20)
scrollToTopOfWindow() // Scroll up after clicking pagination
}
})
2 changes: 1 addition & 1 deletion src/open_inwoner/js/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { CookieBanner } from './cookie-consent'
import './datepicker'
import { Dropdown } from './dropdown'
import './emoji-button'
import './FilterBar/multiselect_listbox_checkbox'
import './FilterBar'
import './form'
import './header'
import './map'
Expand Down
4 changes: 4 additions & 0 deletions src/open_inwoner/scss/components/Button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@
display: flex;
}

&__inner-text {
font-family: var(--font-family-body);
}

> .link__text {
width: 100%;
}
Expand Down
2 changes: 2 additions & 0 deletions src/open_inwoner/scss/components/Cases/Cases.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.cases {
position: relative;

/// cards on cases list
.card {
&__body {
Expand Down
Loading

0 comments on commit 90b41e6

Please sign in to comment.