Files
grecia/app/assets/javascripts/application.js
Javi Martín db25dc13e1 Use buttons to open/close admin navigation submenus
We were using Foundation's accordion menu to open/close nested lists of
links. Unfortunately, Foundation's accordion makes it impossible to
access links in nested links using the keyboard [1] (note the issue is
closed, but in the latest version of Foundation, 6.8.1, it's still
present, and Foundation's development is mostly discontinued).
Furtheremore, it adds the `menuitem` role to links, but ARIA menus are
not ment for navigation but for application behavior and, since it
doesn't add the `menubar` or `menu` roles to the parent elements, it
results in accessibility issues for people using screen readers (also
reported by the Axe accessibility testing engine).

So we need to implement our own solution. We're using the most commonly
used pattern: a buttton with the `aria-expanded` attribute. And, for
people using browsers where JavaScript hasn't loaded, we're keeping the
submenus open at all times (just like we were doing until now), and
we're disabling the buttons (since they do nothing without JavaScript).
This might not be an ideal solution, but it's probably good enough, and
way better than what we had until now.

We've also considered using the <details> and <summary> elements instead
of using buttons to open/close items on the list. However, these
elements still present some accessibility issues [2], and the transition
between open and closed can't be animated unless we overwrite the
`click` event with JavaScript. The pattern of using these elements to
open/close a nested list of links isn't common either, and some people
using screen readers might get confused when entering/leaving the nested
list.

We tried other approaches to get the animation effect, all of them based
on adding `[aria-expanded="false"]:not([disabled]) + * { display: none;
}` to the CSS file.

Unfortunately, animation using CSS isn't feasible right now because
browsers can't animate a change form `height: 0` to `height: auto`.
There are some hacks like animating the `max-height` or the `flex-grow`
property, but the resulting animation is inconsistent. A perfect
animation can be done using the `grid-template-rows` property [3], but
it requires adding a grid container and only works in Firefox and recent
versions of Chrome and similar browsers.

Getting to a solution with JavaScript was also tricky. With the
following approach, `slideToggle()` opened the menu the first time, even
if it was already open (not sure why):

```
toggle_buttons.on("click", function() {
  $(this).attr("aria-expanded", !JSON.parse($(this).attr("aria-expanded")));
  $(this).next().slideToggle();
});
```

This made the arrow turn after the menu had slided instead of doing it
at the same time:

```
toggle_buttons.on("click", function() {
  var button = $(this);

  button.next().slideToggle(function() {
    button.attr("aria-expanded",
    !JSON.parse(button.attr("aria-expanded")));
  });
}
```

With this, everything disappeared quickly:

```
toggle_buttons.on("click", function() {
  var expanded = JSON.parse($(this).attr("aria-expanded"));

  if (expanded) {
    $(this).next().slideUp();
  } else {
    $(this).next().slideDown();
  }

  $(this).attr("aria-expanded", !expanded);
}
```

So, in the end, we're hiding the nested link lists with JavaScript
instead of CSS.

[1] Issue 12046 in https://github.com/foundation/foundation-sites
[2] https://www.scottohara.me/blog/2022/09/12/details-summary.html
[3] https://css-tricks.com/css-grid-can-do-auto-height-transitions
2024-04-18 16:10:58 +02:00

199 lines
6.4 KiB
JavaScript

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery-ujs
//= require jquery-ui/ui/version
//= require jquery-ui/ui/data
//= require jquery-ui/ui/ie
//= require jquery-ui/ui/keycode
//= require jquery-ui/ui/position
//= require jquery-ui/ui/safe-active-element
//= require jquery-ui/ui/scroll-parent
//= require jquery-ui/ui/unique-id
//= require jquery-ui/ui/widget
//= require jquery-ui/ui/widgets/menu
//= require jquery-ui/ui/widgets/mouse
//= require jquery-ui/ui/widgets/datepicker
//= require jquery-ui/ui/i18n/datepicker-ar
//= require jquery-ui/ui/i18n/datepicker-bs
//= require jquery-ui/ui/i18n/datepicker-cs
//= require jquery-ui/ui/i18n/datepicker-da
//= require jquery-ui/ui/i18n/datepicker-de
//= require jquery-ui/ui/i18n/datepicker-el
//= require jquery-ui/ui/i18n/datepicker-es
//= require jquery-ui/ui/i18n/datepicker-fa
//= require jquery-ui/ui/i18n/datepicker-fr
//= require jquery-ui/ui/i18n/datepicker-gl
//= require jquery-ui/ui/i18n/datepicker-he
//= require jquery-ui/ui/i18n/datepicker-hr
//= require jquery-ui/ui/i18n/datepicker-id
//= require jquery-ui/ui/i18n/datepicker-it
//= require jquery-ui/ui/i18n/datepicker-nl
//= require jquery-ui/ui/i18n/datepicker-pl
//= require jquery-ui/ui/i18n/datepicker-pt-BR
//= require jquery-ui/ui/i18n/datepicker-ru
//= require jquery-ui/ui/i18n/datepicker-sl
//= require jquery-ui/ui/i18n/datepicker-sq
//= require jquery-ui/ui/i18n/datepicker-sv
//= require jquery-ui/ui/i18n/datepicker-zh-CN
//= require jquery-ui/ui/i18n/datepicker-zh-TW
//= require jquery-ui/ui/i18n/datepicker-en-GB
//= require jquery-ui/ui/widgets/autocomplete
//= require jquery-ui/ui/widgets/sortable
//= require blueimp-file-upload/js/jquery.iframe-transport
//= require blueimp-file-upload/js/jquery.fileupload
//= require foundation-sites
//= require turbolinks
//= require turbolinks_anchors
//= require ckeditor/loader
//= require_directory ./ckeditor
//= require social-share-button
//= require ahoy
//= require app
//= require check_all_none
//= require comments
//= require foundation_extras
//= require location_changer
//= require moderator_comment
//= require moderator_debates
//= require moderator_proposals
//= require moderator_budget_investments
//= require moderator_proposal_notifications
//= require moderator_legislation_proposals
//= require gettext
//= require annotator
//= require jquery.amsify.suggestags
//= require tags
//= require participation_not_allowed
//= require advanced_search
//= require registration_form
//= require suggest
//= require forms
//= require valuation_budget_investment_form
//= require embed_video
//= require fixed_bar
//= require banners
//= require social_share
//= require checkbox_toggle
//= require markdown-it/dist/markdown-it
//= require markdown_editor
//= require html_editor
//= require cocoon
//= require answers
//= require questions
//= require legislation_admin
//= require legislation
//= require legislation_allegations
//= require legislation_annotatable
//= require legislation_draft_versions
//= require followable
//= require flaggable
//= require documentable
//= require imageable
//= require tree_navigator
//= require tag_autocomplete
//= require polls_admin
//= require leaflet/dist/leaflet
//= require leaflet.markercluster/dist/leaflet.markercluster
//= require map
//= require polls
//= require sortable
//= require table_sortable
//= require investment_report_alert
//= require managers
//= require i18n
//= require globalize
//= require settings
//= require cookies
//= require columns_selector
//= require budget_edit_associations
//= require budget_hide_money
//= require datepicker
//= require authenticity_token_refresh
//= require_tree ./admin
//= require_tree ./sdg
//= require_tree ./sdg_management
//= require custom
//= require_tree ./custom
var initialize_modules = function() {
"use strict";
App.Answers.initialize();
App.Questions.initialize();
App.Comments.initialize();
App.ParticipationNotAllowed.initialize();
App.Tags.initialize();
App.FoundationExtras.initialize();
App.LocationChanger.initialize();
App.CheckAllNone.initialize();
App.AdvancedSearch.initialize();
App.RegistrationForm.initialize();
App.Suggest.initialize();
App.Forms.initialize();
App.ValuationBudgetInvestmentForm.initialize();
App.EmbedVideo.initialize();
App.FixedBar.initialize();
App.Banners.initialize();
App.SocialShare.initialize();
App.CheckboxToggle.initialize();
App.MarkdownEditor.initialize();
App.HTMLEditor.initialize();
App.LegislationAdmin.initialize();
App.Legislation.initialize();
if ($(".legislation-annotatable").length) {
App.LegislationAnnotatable.initialize();
}
App.TreeNavigator.initialize();
App.Documentable.initialize();
App.Imageable.initialize();
App.TagAutocomplete.initialize();
App.PollsAdmin.initialize();
App.Map.initialize();
App.Polls.initialize();
App.Sortable.initialize();
App.TableSortable.initialize();
App.InvestmentReportAlert.initialize();
App.Managers.initialize();
App.Globalize.initialize();
App.Settings.initialize();
if ($("#js-columns-selector").length) {
App.ColumnsSelector.initialize();
}
App.AdminBudgetsWizardCreationStep.initialize();
App.AdminMachineLearningScripts.initialize();
App.AdminTenantsForm.initialize();
App.AdminVotationTypesFields.initialize();
App.AdminMenu.initialize();
App.BudgetEditAssociations.initialize();
App.BudgetHideMoney.initialize();
App.Datepicker.initialize();
App.SDGRelatedListSelector.initialize();
App.SDGManagementRelationSearch.initialize();
App.AuthenticityTokenRefresh.initialize();
};
var destroy_non_idempotent_modules = function() {
"use strict";
App.ColumnsSelector.destroy();
App.Datepicker.destroy();
App.HTMLEditor.destroy();
App.LegislationAnnotatable.destroy();
App.Map.destroy();
App.SocialShare.destroy();
};
$(document).on("turbolinks:load", initialize_modules);
$(document).on("turbolinks:before-cache", destroy_non_idempotent_modules);