Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: refactor dropdown to make it reusable #5675

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog/_5587.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

### Changed

- unify dropdown and refactor following files: dropdown.js, _dropdown.scss, item_detail_dropdown.html, user_indicator.html, _user_indicator.scss

### Removed
- deleted user_indicator.js
82 changes: 53 additions & 29 deletions meinberlin/apps/contrib/assets/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,65 @@
const Dropdown = {
toggleDropdown (event) {
const dropdown = event.currentTarget.parentNode
const isOpen = dropdown.getAttribute('aria-expanded') === 'true'
class Dropdown {
constructor (container) {
this.container = container
this.trigger = container.querySelector('[data-dropdown-trigger]')
this.dropdown = container.querySelector('[data-dropdown-menu]')
this.closeTimeout = null

dropdown.setAttribute('aria-expanded', !isOpen)
this.init()
}

const menu = dropdown.querySelector('.dropdown__menu')
menu.classList.toggle('dropdown__menu--show', !isOpen)
init () {
this.trigger.addEventListener('click', (event) => this.toggleDropdown(event))
this.dropdown.addEventListener('keyup', (event) => this.handleKeyup(event))
this.dropdown.addEventListener('focusout', (event) => this.handleFocusout(event))
}

if (!isOpen) {
const menu = dropdown.querySelector('.dropdown__menu')
menu.firstElementChild.focus()
toggleDropdown (event) {
event.preventDefault()
if (this.container.classList.contains('dropdown--open')) {
this.closeDropdown()
} else {
this.openDropdown()
}
},
}

openDropdown () {
this.container.classList.add('dropdown--open')
this.trigger.setAttribute('aria-expanded', true)
document.addEventListener('click', this.outsideClickListener)
}

closeDropdown (event) {
const dropdowns = document.querySelectorAll('.js-dropdown')
dropdowns.forEach(function (dropdown) {
const isOpen = dropdown.getAttribute('aria-expanded') === 'true'
if (isOpen && !dropdown.contains(event.target)) {
dropdown.setAttribute('aria-expanded', 'false')
const menu = dropdown.querySelector('.dropdown__menu')
menu.classList.remove('dropdown__menu--show')
}
})
},
if (event) event.stopPropagation()
document.removeEventListener('click', this.outsideClickListener)
this.container.classList.remove('dropdown--open')
this.trigger.setAttribute('aria-expanded', false)
clearTimeout(this.closeTimeout)
}

init () {
const dropdowns = document.querySelectorAll('.js-dropdown')
if (dropdowns.length > 0) {
dropdowns.forEach(function (button) {
button.addEventListener('click', Dropdown.toggleDropdown)
})
handleKeyup (event) {
if (event.keyCode === 27) {
this.closeDropdown()
this.trigger.focus()
}
}

handleFocusout (event) {
this.closeTimeout = setTimeout(() => {
if (!this.dropdown.contains(event.relatedTarget)) {
this.closeDropdown()
}
}, 10)
}

document.addEventListener('click', Dropdown.closeDropdown)
outsideClickListener = (event) => {
if (!this.container.contains(event.target) && event.target !== this.close) {
this.closeDropdown()
}
}
}

export default Dropdown
document.addEventListener('DOMContentLoaded', () => {
const dropdowns = document.querySelectorAll('[data-dropdown]')
dropdowns.forEach(dropdown => new Dropdown(dropdown))
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{% load i18n item_tags contrib_tags moderatorremark_tags react_reports %}

<div class="dropdown js-dropdown">
<button title="{% translate 'Actions' %}" type="button" class="dropdown__toggle" aria-haspopup="true" aria-expanded="false" id="idea-{{ object.pk }}-actions">
<div class="dropdown" data-dropdown>
<button data-dropdown-trigger title="{% translate 'Actions' %}" type="button" class="dropdown__toggle" tabindex="0" aria-haspopup="menu" aria-controls="dropdown-menu" id="idea-{{ object.pk }}-actions">
<i class="fas fa-ellipsis-h" aria-label="{% translate 'Actions' %}"></i>
</button>
<nav class="dropdown__menu" aria-labelledby="idea-{{ object.pk }}-actions">
<nav class="dropdown__menu" aria-labelledby="idea-{{ object.pk }}-actions" tabindex="-1" data-dropdown-menu>
<ul class="list--clean">
{% if user_may_change %}
{% get_item_update_url object as change_url %}
Expand Down
63 changes: 0 additions & 63 deletions meinberlin/apps/users/assets/user_indicator.js

This file was deleted.

104 changes: 49 additions & 55 deletions meinberlin/apps/users/templates/meinberlin_users/user_indicator.html
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
{% load i18n static %}

<div class="user-indicator__container">
<div id="js-user-indicator" class="user-indicator__wrapper">
<div class="dropdown" data-dropdown>
{% if request.user.is_authenticated %}
<button
id="js-user-indicator-trigger"
type="button"
aria-haspopup="menu"
aria-controls="js-user-indicator-dropdown"
tabindex="0">
<i class="fas fa-user text--color-primary" aria-hidden="true"></i>
{{ request.user.username }}
</button>
<div role="menu" class="dropdown" id="js-user-indicator-dropdown" aria-label="Login of Register" tabindex="-1">
<button id="js-user-indicator-close" type="button" class="button button--close user-indicator__close" title="{% translate 'close' %}" aria-label="{% translate 'menu close' %}"></button>
<ul class="list--clean">
<li><b>{% translate "My account" %}</b></li>
<li><a href="{% url 'account' %}" role="menuitem">{% translate "Account Settings" %}</a></li>
{% for organisation in request.user.organisations %}
<li><a href="{% url 'a4dashboard:project-list' organisation_slug=organisation.slug %}" role="menuitem">{{ organisation.name }}</a></li>
{% endfor %}
{% if request.user.is_superuser %}
<li><a href="{% url 'meinberlin_platformemails:create' %}" role="menuitem">{% translate "Platform Email" %}</a></li>
{% endif %}
<li>
<form class="block--nogap text--color-dark" action="{% url 'account_logout' %}" method="post" aria-label="{% translate 'Logout' %}" role="menuitem">
{% csrf_token %}
<input type="hidden" name="next" value="{{ redirect_field_value }}">
<button type="submit">{% translate "Logout" %}</button>
</form>
</li>
</ul>
</div>
{% else %}
<button
id="js-user-indicator-trigger"
type="button"
aria-haspopup="menu"
aria-controls="js-user-indicator-dropdown"
tabindex="0">
<i class="fas fa-user text--color-primary" aria-hidden="true"></i>
{% translate "Login" %}
</button>
<div role="menu" class="dropdown" id="js-user-indicator-dropdown" aria-label="Login of Register" tabindex="-1">
<button id="js-user-indicator-close" type="button" class="button button--close user-indicator__close" title="{% translate 'close' %}" aria-label="{% translate 'menu close' %}"></button>
<ul class="list--clean">
<li>
<a class="button button--light" href="{% url 'account_login' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Login" %}</a>
</li>
<li>
<a href="{% url 'account_signup' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Register" %}</a>
</li>
<li>
<a href="{% url 'account_reset_password' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Forgot password?" %}</a>
</li>
</ul>
</div>
{% endif %}
<button data-dropdown-trigger type="button" aria-haspopup="menu" aria-controls="dropdown-menu" tabindex="0">
<i class="fas fa-user text--color-primary" aria-hidden="true"></i>
{{ request.user.username }}
</button>
<div role="menu" class="dropdown__menu" aria-label="{% translate "Account" %}" tabindex="-1" data-dropdown-menu>
<h2>{% translate "My account" %}</h2>
<ul class="list--clean">
<li class="dropdown__item">
<a href="{% url 'account' %}" role="menuitem">{% translate "Account Settings" %}</a>
</li>
{% for organisation in request.user.organisations %}
<li class="dropdown__item">
<a href="{% url 'a4dashboard:project-list' organisation_slug=organisation.slug %}" role="menuitem">{{ organisation.name }}</a>
</li>
{% endfor %}
{% if request.user.is_superuser %}
<li class="dropdown__item">
<a href="{% url 'meinberlin_platformemails:create' %}" role="menuitem">{% translate "Platform Email" %}</a>
</li>
{% endif %}
<li class="dropdown__item">
<form class="block--nogap text--color-dark" action="{% url 'account_logout' %}" method="post" aria-label="{% translate 'Logout' %}" role="menuitem">
{% csrf_token %}
<input type="hidden" name="next" value="{{ redirect_field_value }}">
<button class="button button--light" type="submit">{% translate "Logout" %}</button>
</form>
</li>
</ul>
</div>
{% else %}
<button data-dropdown-trigger type="button" aria-haspopup="menu" aria-controls="dropdown-menu" tabindex="0">
<i class="fas fa-user text--color-primary" aria-hidden="true"></i>
{% translate "Login" %}
</button>
<div role="menu" class="dropdown__menu" aria-label="{% translate "Login or Register" %}" tabindex="-1" data-dropdown-menu>
<ul class="list--clean">
<li class="dropdown__item">
<a class="button button--light" href="{% url 'account_login' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Login" %}</a>
</li>
<li class="dropdown__item">
<a href="{% url 'account_signup' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Register" %}</a>
</li>
<li class="dropdown__item">
<a href="{% url 'account_reset_password' %}?next={{ redirect_field_value|urlencode }}" role="menuitem">{% translate "Forgot password?" %}</a>
</li>
</ul>
</div>
{% endif %}
</div>
</div>
5 changes: 1 addition & 4 deletions meinberlin/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import 'select2' // used to select projects in containers

import '../../apps/actions/assets/timestamps.js'
import '../../apps/newsletters/assets/dynamic_fields.js'
import '../../apps/users/assets/user_indicator.js'
import Dropdown from '../../apps/contrib/assets/dropdown.js'
import '../../apps/contrib/assets/dropdown.js'

// map search function
import 'adhocracy4/adhocracy4/maps/static/a4maps/a4maps_address.js'
Expand All @@ -31,8 +30,6 @@ function init () {
minimumResultsForSearch: -1
})
}

Dropdown.init()
}

document.addEventListener('DOMContentLoaded', init, false)
Expand Down
Loading