wagtail / wagtail

A Django content management system focused on flexibility and user experience
https://wagtail.org
BSD 3-Clause "New" or "Revised" License
18.26k stars 3.86k forks source link

UX improvement pagination in wagtail admin #12004

Open onno-timmerman opened 5 months ago

onno-timmerman commented 5 months ago

Is your proposal related to a problem?

Admin complains in a project that the pagination is not usable in Wagtail admin. It only give you "next page" and "prev page" This is annoying if you have 1000+ items and want to brouwse in it. Explaining that you can change the url to jump is very userfriendly :)

Describe the solution you'd like

Normal usable pagination at the bottom in the form <<<< first page 1, 2, 3, 4, 5, 6 ... 1000, 1001, 1002 - last page >>>

laymonage commented 4 months ago

As an addition, if we want to add this, I think it would be useful to allow the user to input a specific page number to jump to.

This needs some UI designs before it can be implemented, though.

allcaps commented 4 months ago

Jeroen is working on this during Wagtail Space NL 2024, if we have a PR ready with the technical implementation, we always improve the styling/design later.

jordanmt commented 2 months ago

Is this being worked on already? If not, I would be happy to submit a PR with a version of this that I've built.

I currently have a subclassed generic.IndexView where I added Django's get_elided_page_range to the pagination setup. By default, the first two and last two page numbers are included, as well as three pages beside the current page. If the numbers aren't continuous, an ellipsis is added between the sets of numbers.

I kept and bolded "Page X" for the current page to help emphasise it, and dropped the "of Y." since the last page number is always in view at the end.

Here are a few screenshots:

On 2 of 18: Screenshot from 2024-08-17 23-44-13

On 10 of 18: Screenshot from 2024-08-17 23-44-38

On the last page: Screenshot from 2024-08-17 23-44-46

thibaudcolas commented 2 months ago

@jordanmt from @allcaps’ message it sounds like someone else might have an implementation under way?

We do have target designs for this, here is a screenshot:

pagination ui design

I’m not sure how much we’ve considered different options on the emphasis and number of pages to show on the two sides of the ellipsis, will make sure to bring this up in our next UI design meeting.

gasman commented 2 months ago

@thibaudcolas Could I suggest including #11988 (adding an 'items per page' selector) as part of that discussion? Personally I'm not sold on it (it's adding extra UI controls for micromanaging a detail that users shouldn't have to care about) but it'd be good to have a definitive yes or no.

allcaps commented 2 months ago

I think Jeroens efforts during Wagtail Space NL are not continued. So please go ahead.

jordanmt commented 2 months ago

I'll work on this further using the target designs. They look well thought out! I think I'll start it with 1 number on the ends and 2 on the sides and do a closer review on how it looks on different page sizes than I did earlier. I'll also keep an eye out for any further UI discussion.

thibaudcolas commented 2 months ago

Three design questions for @benenright to review:

benenright commented 2 months ago

Hi

I have made some adjustments based on the comments above:

image

thibaudcolas commented 2 months ago

Thank you @benenright, do you have thoughts on the options we should offer under "Items per page"?

For your proposed designs, as-is it seems like the position of the buttons would move around horizontally as you go from page to page – would it be possible to change the designs so they’re more predictable? For example if I start on page 1 and want to go to page 5, it would be nice if the "Next" button was at the same place on the horizontal axis, so I just have to scroll back down to it and click.

allcaps commented 2 months ago

I'd like the 'Items per page' to be defined in code. I think deciding the length of a list is a developer decision. Not something a user should have control over.

The wait for too many items might be annoying for the user, for the hosting platform it might fail to generate a too big response.

Maybe it is me, but I just don't see big UX benefits. What does the user gain? After say 50 items, it is an impractical long list anyway.

benenright commented 2 months ago

Slight update to address Thibaud's comment above:

Image

gasman commented 2 months ago

We probably want to use Django's get_elided_page_range here to avoid reinventing the wheel, and if we do, we can't enforce that there will always be exactly 6 buttons. As long as that block as a whole is centred, though, I think that's good enough...?

tbrlpld commented 3 weeks ago

Slight update to address Thibaud's comment above:

  • use some logic to ensure there are always 6 buttons between next/prev (inc. ellipses where needed to show skipped pages)
  • this means the next/prev and first/last page buttons will always remain in the same position on the horizontal axis

Image

This is looking nice 🧽✨

jordanmt commented 1 week ago

I've built an initial version of this and wanted to share progress and a look at two options. The first is using Django's Paginator.get_elided_page_range with on_each_side and on_ends params set to 1, and the second uses a custom page range.

Using get_elided_page_range from Django's Paginator This on is simple on the Python side. We just need to add get_elided_page_range into the page context so that we can use it in templates (i.e. pagination_nav.html).

https://github.com/user-attachments/assets/56a9ae04-f730-4312-8fe3-41136e045a0d

Using a custom page range that ensures 6 positions This requires more code, but personally I like the consistency along the horizontal axis. You'll see a small shift in this video clip when previous/next show or hide because I haven't perfected the layout yet. Here I am setting paginator_class on BaseListingView to use a Django Paginator subclass with an added custom range function. The function is basically a simplified version of the Django one, assembling the ranges + ellipses in a way that ensures 6 positions.

https://github.com/user-attachments/assets/f902583c-7acf-4f70-88db-2270707e4c36

I'll work on cleaning up the second approach so that the code can be evaluated for any trade offs, and it'll be easy to go back to the first option if that seems best. I'm also going to look at a setting for the paginator class so that it's easier for developers to customize at a high level.

allcaps commented 1 week ago

Great work. Looks really good.

Just wondering, does that second example not depend on word length? What if those words are longer or shorter in some languages? Does this still work? We should consider the shortest and longest translations to validate the 'staying in place'.

And does a visible, but disabled previous and disabled next not prevent jumping? Maybe it is not designed that way, but it is a pretty common solution. The disabled buttons could also be styled to have little visual impact. Just occupy the space.

jordanmt commented 1 week ago

My plan was to use the translated string to maintain the same widths across pages, using visibility: hidden;. I think this is an acceptable approach but I should double check the accessibility implications (I believe it will be hidden from screen readers as well, which would be the goal).

This was my approach:

{% if items.has_previous %}
    <a href="{{ url_path }}{% querystring p=items.previous_page_number %}">
        {% icon name="arrow-left" classname="default" %}
        {% trans 'Previous' %}
    </a>
{% else %}
    <span style="visibility: hidden;"> <!-- doesn't need to be inline CSS, but added here to illustrate -->
        {% icon name="arrow-left" classname="default" %}
        {% trans 'Previous' %}
    </span>
{% endif %}

The issue causing shifting in my video is that I didn't have the icon repeated in the span in this example, only the text. So it was off by about 20px. Now it's totally smooth :).

But, thinking about this further: an A element with the href omitted is OK, so I think we can do something more concise and maintainable (less chance of the span and a elements varying in styling) like this:

<a{% if items.has_previous %} href="{{ url_path }}{% querystring p=items.previous_page_number %}"{% endif %}>
    {% icon name="arrow-left" classname="default" %}
    {% trans 'Previous' %}
</a>

And just select for a elements without href:

.pagination li a:not([href]) {
    visibility: hidden;
}
tbrlpld commented 1 week ago

That looks neat. I like that the "next" and "previous" links stay in place.

To make the links stay in place even if the other one is not shown, could you maybe use flex or grid to define containers in which the links are shown when they are needed?

thibaudcolas commented 1 week ago

Wow! this looks great, and thank you for taking the time to provide the update. Your custom version does seem much smoother.

Re word lengths, I would assume it should be no problem as long as we text-align: end the left label, and text-align: start the right label. Or the flex / grid options that Tibor mentions. Happy to take more of a look once the PR is there!