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

Re-implement chooser modals with new design #9246

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

stevedya
Copy link
Contributor

@stevedya stevedya commented Sep 22, 2022

This draft PR is for converting the current chooser functionality to use the new accessible choosers.

Still left TODO as part of this work:

  • Focus styling on search bar and filter button when tabbed to
  • move initChooserCreationForm and initChooserFilters into methods on ChooserModalOnloadHandlerFactory
  • Focus search input when dialog opens
  • Update tests affected by changes
  • Code review
  • Global Chooser Testing
  • Document upgrade path for third party plugins
  • Add a loading state
  • Keyboard focus for main search field
  • Keyboard focus for filters toggle
  • Make scrolling apparent for images chooser
  • Show pagination outside of scrolling context
  • Back button icon position
  • Focus state for close button
  • Scrollable form excluding header, with form max height
  • Custom placeholder label / dialog label for each chooser type (and snippet type)
  • Locale selector in page chooser
  • Page chooser top-bottom spacing
  • Document chooser min height
  • Open the dialog with a loading icon before loading the contents to avoid a strange delay of opening dialogs with a large request that causes delay before dialog opens
  • Convert the workflow task chooser to use new filter form search bar and creation form toggle

Maybe:

  • Convert page/link choosers to use a dropdown next to the search as seen in the figma file

In Progress Screenshots

Image chooser

Screen Shot 2022-09-29 at 9 48 52 PM

Image chooser with duplicated image

Screen Shot 2022-09-29 at 8 32 08 PM

#Image chooser with open filters

Screen Shot 2022-09-29 at 9 49 03 PM

Snippet chooser with search

Screen Shot 2022-09-29 at 9 49 20 PM

Image chooser update form

Screen Shot 2022-09-29 at 9 48 56 PM

Page chooser

Screen Shot 2022-09-29 at 9 44 31 PM

Choose image format for Draftail images

Screen Shot 2022-09-29 at 8 58 00 PM

@squash-labs
Copy link

squash-labs bot commented Sep 22, 2022

Manage this branch in Squash

Test this branch here: https://stevedyafeaturea11y-choosers-2jmh3.squash.io

@zerolab
Copy link
Contributor

zerolab commented Sep 27, 2022

What is the impact of these changes for third party packages?
If none, amazing! If any, can you document the upgrade path with specific examples?

@gasman
Copy link
Collaborator

gasman commented Sep 29, 2022

Initially I wasn't keen on having a {% dialog %} stub embedded into every single instance of a chooser widget - which could add up to a lot of extra boilerplate on a complex "streamfield from hell" type page... but I'm coming round to the idea that it is OK (or at least, something to be dealt with outside the scope of this PR):

  • The {% dialog %} tag essentially moves the modal dialog page furniture into a Django template where it was previously embedded in modal-workflow.js, and from a maintainability POV I can't really complain about that...
  • Rendering this furniture once globally, or once per chooser type, would be preferable to once per chooser instance, but we do want the Chooser widget class to behave as a normal standalone Django form widget, and not depend on additional external conditions like "if you're going to use this widget in your form, the template must also output this HTML declaration with id foo". Essentially, to make that approach play well with Django forms, we'd need something that works like form media (i.e. generates a single declaration shared by all widget instances), but with arbitrary HTML fragments rather than just JS/CSS filenames. That something doesn't exist in Django forms.
  • Consequently: short of totally overhauling the Django forms framework, we can't escape the fact that any rendered form widget has to provide its own copy of any HTML fragments it intends to use.
  • Long term, though, we do have a plan to overhaul the Django forms framework - namely, Telepath. If we extend the approach we currently use for StreamField to work with the edit form as a whole, we'll decouple the form data from the once-per-field JS/HTML declarations, and reconcile them in client-side code - which means we avoid serving a separate copy of this HTML for each element.

@stevedya
Copy link
Contributor Author

stevedya commented Sep 30, 2022

@gasman I've made quite a few updates since we last chatted about these changes and I do have some good news 🙂

After tinkering with the {% dialog %} inside of every chooser element approach, I ultimately ended up revising this to keep a similar pattern to what we have currently. This now only renders one global dialog template for choosers inside of dialog.js . And modal-workflow.js uses the functions of the new Dialog. So that should help address those concerns 🎉

self.body.html(response.html);
self.container.modal('show');
self.container.show();
self.body.innerHTML = response.html;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly, jquery's html(...) function has the hidden feature of executing any <script> tags in the HTML content, which innerHTML doesn't do. If that is the case, we should check whether any of our modals are relying on inline <script>s (I suspect they are...) and ensure we reinstate that behaviour.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah jQuery does do that. It’s not too hard to replicate with vanilla JS but I think we could just stick to jQuery’s .html for the time being (and add a comment that this is for good reason).

import {initTabs} from './tabs';
import {initTooltips} from './initTooltips';
import initChooserCreationForm from './initChooserCreationForm';
import initChooserFilters from "./chooserFilters";
Copy link
Collaborator

@gasman gasman Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be neater to make initChooserCreationForm and initChooserFilters into methods on ChooserModalOnloadHandlerFactory, so that subclasses of ChooserModalOnloadHandlerFactory can customise them where appropriate?

(ah, now I see that initChooserCreationForm is called on page load too. Does that imply that "chooser creation forms" exist outside of chooser modals?)

Looks like that would also avoid having to expose ajaxify-chooser-links as a custom event, which I'm not entirely keen on - 'ajaxifying' feels like too much of an internal implementation detail of choosers to be appropriate to expose to external code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing this 😃 Thats a great idea. It definitely would be a lot neater to have them inside of the ChooserModalOnloadHandlerFactory Chooser creation forms/toggling don't exist outside of the choosers so we definitely wouldn't need them on page load then either yep.

I agree on removing the ajaxify-chooser-links event because we won't need it then either. I'll add this to my TODO list

@@ -290,9 +303,11 @@ class ChooserModalOnloadHandlerFactory {
}

onLoadReshowCreationFormStep(modal, jsonData) {
$(this.creationFormTabSelector, modal.body).replaceWith(
$(this.creationFormTabSelector, modal.body).html(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

html() sets the inner content of the creationFormTabSelector element, while replaceWith replaces it. Given that the data-chooser-creation-form-wrapper element that creationFormTabSelector refers to is the top-level element of creation_form.html - and will thus appear in the htmlFragment - won't this change mean that we end up with two nested data-chooser-creation-form-wrapper elements?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes there is a chance for duplicated nested elements if a creation_form is returned for sure. I may have to revisit this then.

I had originally made this change to avoid having to re-add the same classes / data attributes to all the potential nested templates. For example confirm_duplicate_upload.html when using replaceWith() will also need to have these attributes/classes for styling/events.

  class="w-chooser-creation-form"
  data-chooser-creation-form-wrapper

I will also have to re-update event listeners when the element is replaced if we went with replaceWIth() (not a problem)

I would be interested in knowing all the possible templates that onLoadReshowCreationFormStep could potentially send back.

@stevedya
Copy link
Contributor Author

stevedya commented Oct 3, 2022

What is the impact of these changes for third party packages? If none, amazing! If any, can you document the upgrade path with specific examples?

@zerolab Sorry for the delayed reply. This is a good idea! I did try to keep as much similar as I could but i'm sure some notes on the upgrade path will still be needed because of the vast amount of changes. I've put this as a TODO on this PR now. Thanks!

@@ -330,6 +330,16 @@
}
}

// For buttons we want to look like inline links
&.plain {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add these to the pattern library and styleguide template?

Note: We actually recently removed something similar unbutton as it was not used, not sure if we should revisit this class that was already present in the code instead of adding a new name?

Alternatively, should we name this to be a bit clearer - .as-link or w-button--as-link (for longer term adoption of BEM classes). Or does this get close enough if we add .link to the button?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me like something that might not need to live in this file as a generic button style to start with, since (at least currently) it’s very specific to choosers?

<header class="w-chooser-header">
<div class="w-chooser-header__wrapper">
<div class="w-chooser-header__search-field">
<form class="search-form search-form" action="{% url "wagtailadmin_choose_page_search" %}?page_type={{ page_type_string }}" method="get" novalidate role="search">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta remove this duplicated class oops

@@ -330,6 +330,16 @@
}
}

// For buttons we want to look like inline links
&.plain {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me like something that might not need to live in this file as a generic button style to start with, since (at least currently) it’s very specific to choosers?

}
}

.w-chooser-listing {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably use its own file.


}

.w-chooser {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this could be worth its own file.

self.body.html(response.html);
self.container.modal('show');
self.container.show();
self.body.innerHTML = response.html;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah jQuery does do that. It’s not too hard to replicate with vanilla JS but I think we could just stick to jQuery’s .html for the time being (and add a comment that this is for good reason).

};

const template = `
<div aria-hidden="true" id="${id}" aria-labelledby="title-${id}" class="w-dialog ${isChooser && 'w-dialog--chooser'}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would need a comment to explain why we have this HTML in JS.

{% for tag in popular_tags %}
<option value="{{ tag.name }}">{{ tag.name }}</option>
{% endfor %}
</select>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤟

<div class="w-dialog__message w-dialog__message--warning">
{% icon name='warning' class_name="w-dialog__message-icon" %}
<div class="w-dialog__message-header">
<p class="w-dialog__message-description ">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p class="w-dialog__message-description ">
<p class="w-dialog__message-description">

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enhance

font-size: 3rem;
.icon {
width: 100%;
max-height: theme('spacing.9');
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn’t we have to keep all styles here as-is for tables listing?

}

.w-chooser__body[aria-hidden="true"] {
transform: translateX(-100%);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work in RTL mode?

@thibaudcolas thibaudcolas added this to the 4.1 milestone Oct 7, 2022
@gasman gasman modified the milestones: 4.1, 4.2 Oct 17, 2022
@thibaudcolas thibaudcolas changed the title Draft: A11y choosers setup and styling Draft: Re-implement chooser modals with new design Nov 17, 2022
@thibaudcolas thibaudcolas changed the title Draft: Re-implement chooser modals with new design Re-implement chooser modals with new design Nov 23, 2022
@thibaudcolas
Copy link
Member

We’re currently intending to pick this up in either the February or May 2023 releases. Here is a record of (potentially) related chooser issues and pull requests which I expect we will want to make updates to once this is merged.

I’ve classified them by chooser though there’s a high likelihood quite a few of those apply to more choosers than reported.

All choosers

Page/link chooser

Document chooser

Image chooser

Generic chooser

PRs

@lb-
Copy link
Member

lb- commented Nov 23, 2022

Will there be any plans to revisit the JS approach in the context of Stimulus adoption? I've kind of kept that out of scope for the Outreachy project for now but have a few ideas about chooser implementations that would leverage Stimulus.

Let me know if it's worth a POC or some notes

@thibaudcolas
Copy link
Member

@lb- we’re planning to revisit the JS but haven’t discussed further how exactly to go about that.

@gasman gasman modified the milestones: 4.2, 4.3 Jan 18, 2023
@dkirkham
Copy link

dkirkham commented Feb 4, 2023

I'm not sure where this request lands in the 30-odd issues listed above - quite a few places possibly - but there are two things I'd find really useful in redeveloped choosers:

  • a unified link chooser dialog that could be called from inside a RichTextField for inline links, and which could also be called from a streamfield Block for rendering, for instance, as a button. A features field could be used to control the types of links that can be selected, (ie. pages, documents, email, phone, anchor) in a similar way to the features within a RichTextField itself.
  • the page chooser function (both alone and as part of a unified link dialog) supporting links to anchors in Wagtail managed pages.

Also I think worth considering for the chooser dialogs:

@lb- lb- mentioned this pull request Feb 23, 2023
@thibaudcolas thibaudcolas removed this from the 5.0 milestone Apr 19, 2023
@lb-
Copy link
Member

lb- commented Feb 18, 2024

@stevedya @thibaudcolas - what's the status of this work? Is this something that still aligns with the frontend / UI strategy?

A lot has changed since this PR was opened so not sure on the best way forward.

Multiple parts of the JS impacted here has changed with Stimulus adoption, formalising support for RTL layouts, larger reworking of choosers that have since happened, and an extensive amount of rework of base listings & generic chooser viewsets.

Is there a way we can split out some parts of the goals here into smaller issues that can be picked up by a wider range of contributors?

Maybe there's parts that make sense as part of further inline script removal / Stimulus migration of choosers also.

I have a call lined up with @thibaudcolas and @laymonage for a fortnight's time, maybe this is a good discussion item if we get this happening.

@dkirkham you probably already know but there are lots of improvements to the way generic choosers can be build with Chooser Viewsets since this PR was raised. https://docs.wagtail.org/en/stable/reference/viewsets.html#chooserviewset

However, other items are still outstanding from what I can see.

@dkirkham
Copy link

@lb-, I don't believe any of my wish list have been implemented. Do you think it is worth writing them up as feature requests so they are not lost?

@lb-
Copy link
Member

lb- commented Feb 18, 2024

Thanks @dkirkham - I must not have read your comments correctly then.

Anyway, yes I think think an issue or two-ish to clearly lay out the enhancements you'd like to see would be helpful. Doesn't mean it will be built but it can help guide future directions, external packages or APIs even if not solved directly as requested.

It's also a good way to have a discussion with others that may want to see similar features outside of a PR.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Blocked
Development

Successfully merging this pull request may close these issues.

None yet

6 participants