Tuesday, March 7, 2023

PowerApps: Pagination Component

Hello Friends,
Welcome back with another post on PowerApps. We had discussed about pagination in some of our earlier posts. Today, we will try to create a component, which will provide the pagination feature and can be used with any gallery / data table. This component will provide you the features-
  1. To show current 10 pages (1,2...10 or 11,12...20).
  2. To show Prev / Next button with show / hide feature based upon the page you are currently visiting.
  3. To show the total number of items.
  4. To select the Page Size (number of items you want to show on one page). (Homework For You) 😊
Let's start-
  1. Login to PowerApps Maker Portal and create a new blank canvas app and give a suitable name.
  2. Click on Components and then click on "+ New component" to add a component. Set properties of component screen as-
    1. Name: SJPagination
    2. Width: 1000
    3. Height: 50
    4. Fill: RGBA(0, 0, 0, 1)
  3. Add below custom properties-
    1. Total Items
      1. Display Name: Total Items
      2. Name: TotalItems
      3. Property Type: Input
      4. Data Type: Number
    2. Page Size
      1. Display Name: Page Size
      2. Name: PageSize
      3. Property Type: Input
      4. Data Type: Number
    3. Page Number
      1. Display Name: Page Number
      2. Name: PageNumber
      3. Property Type: Output
      4. Data Type: Number
  4. Click on Component name (SJPagination from left tree view and set the default values for above input properties-
    1. TotalItems: 100
    2. PageSize: 10
  5. Add a Text label to show number of items.
    1. Name: lblTotalItems
    2. Text: Concatenate(Text(SJPagination.TotalItems)," items")
    3. Align: Align.Center
    4. LineHeight: 1
    5. X: SJPagination.Width-Self.Width
    6. Y: 0
    7. Width: 120
    8. Height: SJPagination.Height
    9. PaddingTop: 0
    10. PaddingBottom: Self.PaddingTop
    11. PaddingLeft: Self.PaddingTop
    12. PaddingRight: Self.PaddingTop
    13. Color: RGBA(0, 0, 0, 1)
    14. Fill: RGBA(255, 255, 0, 1)
  6. Add Blank horizontal gallery and set properties as below-
    1. Name: gal_BHG_Pagination
    2. X: (SJPagination.Width-Self.Width)/2
    3. Y: 0
    4. Width: lblTotalItems.X/2
    5. Height: SJPagination.Height
    6. BorderColor: SJPagination.Fill
    7. TemplateSize: Self.Width/10
    8. TemplatePadding: 0
    9. Items: Sequence(10,1,1) //Will Update Later
  7. Add a Button and set it's properties as below-
    1. Name: btnPrevious
    2. Text: "< prev"
    3. X: gal_BHG_Pagination.X-Self.Width
    4. Y: 0
    5. Width: 80
    6. Height: SJPagination.Height
    7. Fill: RGBA(0, 0, 0, 0)
    8. BorderThickness: 0
    9. RadiusTopLeft: 0
    10. RadiusTopRight: Self.RadiusTopLeft
    11. RadiusBottomLeft: Self.RadiusTopLeft
    12. RadiusBottomRight: Self.RadiusTopLeft
    13. DisabledColor: Self.Color
    14. DisabledFill: Self.Fill
    15. PressedColor: Self.Color
    16. PressedFill: Self.Fill
    17. HoverColor: lblTotalItems.Fill
    18. HoverFill: Self.Fill
  8. Add another Button and set properties as below-
    1. Name: btnNext
    2. Text: "next >"
    3. X: gal_BHG_Pagination.X+gal_BHG_Pagination.Width
    4. Y: 0
    5. Width: 80
    6. Height: SJPagination.Height
    7. Fill: RGBA(0, 0, 0, 0)
    8. BorderThickness: 0
    9. RadiusTopLeft: 0
    10. RadiusTopRight: Self.RadiusTopLeft
    11. RadiusBottomLeft: Self.RadiusTopLeft
    12. RadiusBottomRight: Self.RadiusTopLeft
    13. DisabledColor: Self.Color
    14. DisabledFill: Self.Fill
    15. PressedColor: Self.Color
    16. PressedFill: Self.Fill
    17. HoverColor: lblTotalItems.Fill
    18. HoverFill: Self.Fill
  9. Now, we will add controls in gallery (gal_BHG_Pagination). Click on Edit Template icon and add a button. Set the properties of button as below-
    1. Name: btnPageNumber
    2. Text: ThisItem.Value
    3. X: 0
    4. Y: 0
    5. Width: Parent.Width/10
    6. Height: Parent.TemplateHeight-5
    7. Color: If(ThisItem.Value=SJPagination.PageNumber,lblTotalItems.Fill,RGBA(255, 255, 255, 1))
    8. Fill: RGBA(0, 0, 0, 0)
    9. BorderThickness: 0
    10. RadiusTopLeft: 0
    11. RadiusTopRight: Self.RadiusTopLeft
    12. RadiusBottomLeft: Self.RadiusTopLeft
    13. RadiusBottomRight: Self.RadiusTopLeft
    14. DisabledColor: Self.Color
    15. DisabledFill: Self.Fill
    16. PressedColor: Self.Color
    17. PressedFill: Self.Fill
    18. HoverColor: Self.Color
    19. HoverFill: lblTotalItems.Fill
    20. OnSelect: Set(vrSelectedPageNumber,If(ThisItem.Value<=0,1,ThisItem.Value))
  10. Now add a Rectangle and set below properties-
    1. Name: rctPageNumberSelected
    2. X: 0
    3. Y: btnPageNumber.Y+btnPageNumber.Height
    4. Width: btnPageNumber.Width
    5. Height: Parent.TemplateHeight-btnPageNumber.Height
    6. Fill: If(ThisItem.Value=SJPagination.PageNumber,lblTotalItems.Fill,RGBA(0, 0, 0, 1))
  11. Our basic preparation has been done. Now, we will focus on actual functionality.
  12. First, we will set the value of PageNumber property of component. For this, click on component SJPagination and then select the PageNumber property (Output Property). Update the function as-
    1. If(
          IsBlank(vrSelectedPageNumber) || IsBlank(SJPagination.PageSize) || SJPagination.PageSize = 0,
          1,
          If(
              vrSelectedPageNumber > RoundUp(
                  SJPagination.TotalItems / SJPagination.PageSize,
                  0
              ),
              1,
              vrSelectedPageNumber
          )
      )
    2. Basically, it is first validating if vrSelectedPageNumber (currently selected page number) is blank or the PageSize property (Input property) is blank or the PageSize property is 0. If any of the condition is true, then it is setting the PageNumber property as 1 otherwise it is validating if the vrSelectedPageNumber variable value is greater than the total number of pages itself (It needs to be validated in case of filter criteria applied. For example, initially the total number of pages were 25 and the user was currently on page number 24. After applying filter criteria, the number of pages reduced to 13. In such case, there will be no page number 24. Therefore, user must be shown the records of first page). If Yes, then it is again setting the PageSize property as 1 otherwise the vrselectedPageNumber.
  13. Now, we will update the Items property of gal_BHG_Pagination gallery. Ideally, it's items must change as per the initial, previous, next condition. Means, initially, it must show 1...10. If moving to 11th page, it must show 11...20. Then, in case, moving back to 10th page, it must show 1...10 page. So, click on gal_BHG_Pagination and select the Items property from the dropdown and update the logic as below-
    1. If(
          !IsBlank(SJPagination.TotalItems) && !IsBlank(SJPagination.PageSize) && SJPagination.PageSize > 0,
          If(
              RoundUp(SJPagination.TotalItems / SJPagination.PageSize, 0) > 0,
              If(
                  SJPagination.PageNumber > RoundUp(SJPagination.TotalItems / SJPagination.PageSize, 0),
                  Sequence(10,1),
                  Sequence(
                      1 + 
                      If(
                          SJPagination.PageNumber = RoundUp(SJPagination.TotalItems / SJPagination.PageSize, 0
                          ),
                          SJPagination.PageNumber,
                          If(
                              If(
                                  Mod(SJPagination.PageNumber, 10) = 0,
                                  RoundDown(SJPagination.PageNumber / 10, 0) * 10 - 9,
                                  RoundDown(SJPagination.PageNumber / 10, 0) * 10 + 1
                              ) + 9 <= RoundUp(SJPagination.TotalItems / SJPagination.PageSize, 0),
                              If(
                                  Mod(SJPagination.PageNumber, 10) = 0,
                                  RoundDown(SJPagination.PageNumber / 10, 0) * 10 - 9,
                                  RoundDown(SJPagination.PageNumber / 10, 0) * 10 + 1
                              ) + 9,
                              RoundUp(SJPagination.TotalItems / SJPagination.PageSize, 0)
                          )
                      ) - If(
                          Mod(SJPagination.PageNumber, 10) = 0,
                          RoundDown(SJPagination.PageNumber / 10, 0) * 10 - 9,
                          RoundDown(SJPagination.PageNumber / 10, 0) * 10 + 1
                      ),
                      If(
                          Mod(SJPagination.PageNumber, 10) = 0,
                          RoundDown(SJPagination.PageNumber / 10, 0) * 10 - 9,
                          RoundDown(SJPagination.PageNumber / 10, 0) * 10 + 1
                      )
                  )
              )
          )
      )
  14. Let's understand this logic. Dividing the logic into colors to explain in better way.
    1. Color: First check if the current selected page number is greater than the total number of pages. If yes, then create a sequence of 1...10. (However, it will never show the 10 number because the Current Page Number will reset to 1, the moment total number of pages goes beyond the current page number (generally the filter case)).
    2. Color: We are adding 1 so that when the sequence function will create the page number, it starts with 1, 21, 31 and so on because 30-21 will give 9 while we need 10 number for pagination.
    3. Color: If the Current page number is equal to the Total number of pages, then use the same otherwise apply the color block.
    4. Color: This block of code is validating the end number of sequence whether it is less than or equal to the total number of pages. If yes, the use the same otherwise use the total number of page. The reason is that you cannot go beyond the total number of pages. For example if Total Items are 250 and Page Size is 10 the the Total Number of Paes are 25. So the visible pages will be 1-10, 11-20, 21-25. It cannot be 21-30.
    5. Color: It will give you the start page number of the current sequence (explanation given below for color)
    6. Color: This piece of code is defining the start number of sequence. It is saying that- If the Current Page number is 10, 20, 30... then the true part will return 1, 11, 21... otherwise false part will return 1, 11, 21... Because if the Current Page number is lying between 1 - 10, then the sequence must start with 1. Similarly, if the Current Page number lying between 11 - 20, then the sequence must start with 11.
    7. Overall Explanation: The overall objective is to create a sequence of page numbers like 1-10, 11-20, 11-30 and so on until the last page number reaches. If total pages are 40 then the last series will be 31-40. If the total pages are 46 then the last series will be 41-46.
  15. Now, we will update the OnSelect property of btnPrevious.
    1. If(
          IsBlank(vrSelectedPageNumber),
          Set(vrSelectedPageNumber, 1),
          If(
              vrSelectedPageNumber > 1,
              Set(vrSelectedPageNumber, Value(vrSelectedPageNumber) - 1)
          )
      )
  16. Similarly, update the OnSelect property of btnNext.
    1. If(
          !IsBlank(SJPagination.PageSize) && SJPagination.PageSize > 0,
          If(
              SJPagination.PageNumber < RoundUp(
                  SJPagination.TotalItems / SJPagination.PageSize,
                  0
              ),
              Set(
                  vrSelectedPageNumber,
                  SJPagination.PageNumber
              );
              Set(
                  vrSelectedPageNumber,
                  vrSelectedPageNumber + 1
              );
          )
      )
  17. Now, the Previous button must be visible only if PageNumber is greater than 1. Similarly, the Next button must be visible if the PageNumber is less than the Total Number of Pages. 
  18. Update the Visible property of btnPrevious-
    1. SJPagination.PageNumber > 1
  19. Update the Visible property of btnNext-
    1. If(
          !IsBlank(SJPagination.PageSize) && SJPagination.PageSize > 0,
          SJPagination.PageNumber < RoundUp(
              SJPagination.TotalItems / SJPagination.PageSize,
              0
          ),
          false
      )
  20. That's all. Now, go to screen tab, add a gallery add few buttons and create collection of different row counts on these buttons. Assign this collection to Items property of gallery. Add the pagination component. Set TotalItems property to rowcount of collection. Set PageSize as 10. Now play the app and test multiple scenarios.
  21. (Added Apr 01, 2023) The Items property of the gallery should be set as-
    1. Filter(FirstN(collItems,SJPagination_3.PageNumber*10),!(ID in ShowColumns(FirstN(collItems,(SJPagination_3.PageNumber-1)*10),"ID")))
  22. (Added May 22, 2023) Another fast response way to implement the Items property is -
    1. If(
          SJPagination_3.PageSize * SJPagination_3.PageNumber <= CountRows(collItems)
          ,LastN(FirstN(collItems, SJPagination_3.PageSize * SJPagination_3.PageNumber), SJPagination_3.PageSize * 1)
          ,LastN(FirstN(collItems, SJPagination_3.PageSize * SJPagination_3.PageNumber), (CountRows(collItems) - SJPagination_3.PageSize * (SJPagination_3.PageNumber - 1)))
      )
  23. Any of the Items property can be chosen. However, I would prefer the later one as it was found to be more effective, and fast as compared to former one in case of bulk data.
  24. Clicked on 80 Items Coll button.
  25. Clicked on Page No 2.
  26. Clicked on last page number (8).
  27. Clicked on 160 Items Coll button.
  28. Moved to last page of the new collection.
  29. Clicked the 80 Items Coll button again. As the current page number (16) was greater than the total number of pages (8) of new collection. The selected page number resets to 1.
  30. This is how, you can create pagination component. Now, using the output property of component "PageNumber", you can reload the collection by applying filter on datasource. Ideally, your master collection will be the separate one. From that master collection, you have to filter the desired number of items into a child collection and that child collection will be passed to the gallery items property. The pagination component's TotalItems property will be set basis upon master collection.
  31. If you wish to show the TotalItems like "1 - 10 of 80 items", then you need to make couple of changes-
    1. Add 2 buttons-
      1. Name: btnFirst (Copy of btnPrevious)
        1. Text: << first
        2. X: 0
        3. Width: 85
        4. OnSelect: Set(vrSelectedPageNumber, 1)
      2. Name: btnLast (Copy of btnNext)
        1. Text: last >>
        2. X: btnNext.X+btnNext.Width
        3. Width: 85
        4. OnSelect: Set(vrSelectedPageNumber,RoundUp(SJPagination.TotalItems / SJPagination.PageSize,0))
  32. Also, you need to update few properties of existing controls-
    1. btnPrevious.X: btnFirst.X + btnFirst.Width
    2. gal_BHG_Pagination: btnPrevious.X + btnPrevious.Width
  33. Also, you need to add 2 input properties in component-
    1. Name: FromItemNumber / DataType: Number / Default Value: 0
    2. Name: ToItemNumber / DataType: Number / Default Value: 0
  34.  Then you need to update the lblTotalItems controls as-
    1. Text: If(
          SJPagination.TotalItems > 0
          ,SJPagination.FromItemNumber & " - " & SJPagination.ToItemNumber & " of " & SJPagination.TotalItems & " item" & If(SJPagination.TotalItems > 1, "s")
          ,"0 items"
      )
      
    2. Width: 220
    3. Size: 12 (Font size)
  35. Coming back to Screen, where the component is used, we have to give input values to the 2 input parameters we had created.
  36. FromItemNumber : If(CountRows(collItems) > 0, ((SJPagination_3.PageNumber - 1)* SJPagination_3.PageSize + 1), 0)
  37. ToItemNumber : (((SJPagination_3.PageNumber - 1) * SJPagination_3.PageSize) + Gallery1_1.AllItemsCount)
  38. Save the app and play-
  39. Default-
  40. Next-
  41. Last-
  42. Previous-
  43. First-
  44. In case, if you wish to provide Page Size option as well, then you need to add a dropdown and wherever we have used 10 as static value for PageSize, use the selected value of dropdown. Also change the type of PageSize property from Input to Output.
With this, I am concluding this post.
Happy Coding !!!
Will see you again with some new topics.

Stay Safe !
Stay Healthy !

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.