EdJoPaTo / grammy-inline-menu

Inline Menus for Telegram made simple. Successor of telegraf-inline-menu.
MIT License
357 stars 44 forks source link

V5 - pagination #102

Closed lamuertepeluda closed 3 years ago

lamuertepeluda commented 4 years ago

Describe your goal

I'd like to understand how paginationworks. Or if it's better to resolve to choose, or some other lower-level construct in my case.

What I am trying to implement is a menu where there are paginated list and detail "views".

Is it possible with the current pagination implementation? Or would I need to create a new menu component, say "browse"?

Expected

I post some screenshots of a concept that I had "handcrafted" myself some time ago. It had some issues, but it was implementing my desired behavior at last.

Figure 1: First page page_1

In this figure you see a first row of selection buttons (each number correspond to the list item), a Close button (or "Back" if this was a submenu) and a "Next" button with '>'. Of course I could have added also a Last '>>'. I could have used emojis like for your pagination component, but this is a matter of taste and could be easily done. Page size is 3. Larger page sizes would mess with button layout in my opinion, but this is also a matter of how the menu gets rendered. The list item - button label consistence would of course be left to the developer responsibility (could be 'a', 'b', 'c', whatever).

Figure 2: Another page page_2 This is the second page: again we have a first row of selection buttons corresponding to list items, and this time a "Previous" button '<'.

Figure 3: Detail View

detail

This is the (sub) menu you get when you select a list item. In this case I have selected button '1' of the first page, although the label "Mission 7" is misleading. If I press the first button (whatever controls the details card needs) I can interact with the card, otherwise I go back to the page I was browsing before by pressing "Back". I think this is a standard submenu behavior.

The Question

Sorry for the long issue. What I'd like to understand is this: is it possible to implement such behavior with the current pagination menu? I have coded a bit with pagination, and I was not able to get such behavior so far. The documentation is not complete yet, but I understand it is a work in progress. The biggest issue I am facing is displaying these "selection" buttons for detail navigation.

EdJoPaTo commented 4 years ago

As far as I understand this now its basically a first row to open a submenu of the relevant item and a second row to go through the pages? Correct me when I have misunderstood something there.

I think this can already be done via chooseIntoSubmenu as it has integrated its own pagination. The buttons are already managed by the included pagination. You only need to use the page to display only the relevant parts in the menu body.

const ENTRIES_PER_PAGE = 3

function getAllEntries(context: MyContext): string[] {
  return ['Mission 1', 'Mission 2', …]
}

function menuBody(context: MyContext): Body {
  const pageIndex = (context.session.page ?? 1) - 1
  const allEntries = getAllEntries(context)
  const currentPageEntries = allEntries.slice(pageIndex * ENTRIES_PER_PAGE, (pageIndex + 1) * ENTRIES_PER_PAGE)
  let text = 'This is the result text\n'
  text += currentPageEntries.join('\n')
  return text
}

const menuTemplate = new MenuTemplate<MyContext>(menuBody)

menuTemplate.chooseIntoSubmenu('details', getAllEntries, detailsMenuTemplate, {
  maxRows: 1,
  columns: ENTRIES_PER_PAGE,
  getCurrentPage: context => context.session.page,
  setPage: (context, page) => {
    context.session.page = page
  }
}

You mentioned missing documentation. What would you like to have? Whats still missing? Feel free to also write down documentation that would help you and submit a Pull Request. We can then fine tune it and add it so future users can get it more easily. :)

lamuertepeluda commented 4 years ago

Ok thanks for the hint of using chooseIntoSubmenu.
How does it compute the total? It does not seem to have the option, as pagination does.

In my case I am going to fetch each page from an API which returns the totalItems, the total number of elements, and the items of the requested page, giving the pageSize and skipCount variables, which I hold in the session. (skipCount = pageSize * pageIndex), splicing the elements into a session variable items on each API call. Using getTotalPages of pagination I was setting the number of pages available, which I compute as totalPages = Math.ceil(totalItems / pageSize)): I think here it is computed by the length of the entries, right?

Anyway I am going to rewire my code to make it work with chooseIntoSubmenu, which seems the correct approach to me. I am seeing some inconsistencies in the navigation buttons, however before opening an issue about that, I want to make sure that my code is correct.

For the documentation, I was referring to this kind of behavior, which I think can be a rather common use-case. Once I'm finished I'll try to submit a Pull Request with either an example or some extra details in the documentation.

EdJoPaTo commented 4 years ago

As you guessed right, the choices.length with maxRows and columns results basically in your calculation: Math.ceil(choices.length / (maxRows * columns)).

Maybe having an option to override the total pages could be interesting in cases like yours? Paginated APIs often tell you the total pages and you query an exact page. On the other hand: choices are currently validated. When a user presses a button its first validated if the choice pressed is still available. Because of that all the available choices are checked, not only the ones of the current page.

pagination had some bugs which were fixed in beta.9. Maybe these inconsistencies are from that? Please check of you are using the latest beta there.

Thanks for taking the time to do the feedback! :)

lamuertepeluda commented 4 years ago

I was already on beta 9 πŸ˜ƒ What I'm still trying to figure out is the kind of buttons for navigations it is returning me. But I'm not still sure about that, perhaps is my code.

About the length, I found this solution for overriding that behavior:

The code below assumes you have already set the total and stored it in a session variable, e.g. after calling getAllEntries in menuBody in your previous snippet:

menuTemplate.chooseIntoSubmenu('details', 
 context => {
      const total = context.session.total 
      // Create an array of values [1, ...total]
      return [...Array(total + 1).keys()]
        .slice(1, total + 1)
        .map((n) => `M${n}`);
  }, 
  detailsMenuTemplate, {
  maxRows: 1,
  columns: ENTRIES_PER_PAGE,
  getCurrentPage:context => context.session.page,
  setPage: (context, page) => {
    context.session.page = page
  }
}

It will print 'M1', 'M2', 'M3' as buttons (with page size 3), while computing the pages correctly.

lamuertepeluda commented 4 years ago

well perhaps it's a bit more complex than that πŸ˜… I will post a more refined solution

lamuertepeluda commented 4 years ago

I mean, the problem in my above code is that it will show always 'M1', 'M2', 'M3', even on 2nd and following pages.

That's not what I wanted to achieve... 😞

lamuertepeluda commented 4 years ago

I finally resolved to use choose, implementing my own navigation choices for page navigation. It was easier this way.

So chooseIntoSubmenu for item selection, and choose for navigation.

Using this approach I could get exactly the behavior described in the screenshots πŸ˜ƒ

I will try to make a simplified example code and post it here.

EdJoPaTo commented 4 years ago

It would be nice to have either a custom pagination button layout or a simple way to use manualRow in combination with a self defined action. The second one would be also very powerful with other kinds of custom buttons (or button sets).

EdJoPaTo commented 3 years ago

v6.2 adds menuTemplate.manualAction