DEV Community

Aline Ramos
Aline Ramos

Posted on

Componente de Paginação Customizável com RubyUI + Pagy

Recentemente, comecei a desenvolver algumas aplicações menores usando o RubyUI, com o objetivo de aprender mais sobre desenvolvimento de views aproveitando ao máximo o que o Rails oferece. Este pode ser o primeiro de alguns posts onde compartilho soluções legais que consegui criar ao combinar tecnologias do ecossistema Rails para otimizar o desenvolvimento de interfaces.

Neste artigo, vou mostrar como criei uma view de paginação reutilizável, usando os componentes do RubyUI junto com a gem Pagy.

Obs.: O Pagy já oferece helpers prontos para uso direto em HTML/ERB, mas achei interessante criar meu próprio componente para poder estilizar a paginação do meu jeito e criar variações conforme as necessidades do projeto.


Image description


Pré-requisitos

  1. Instalar o RubyUI.

  2. Adicionar gem Pagy (por motivos de performance, é a gem mais indicada atualmente para paginação).
    gem 'pagy', '~> 9.3'

  3. Adicionar alguma biblioteca de icons (escolhi a lucide_icon, mas fica a critério de vocês).
    gem 'lucide-rails', '~> 0.5.1'


Implementação

  • Importei os componentes prontos de Paginação e Botões.
rails g ruby_ui:component Button
rails g ruby_ui:component Pagination
Enter fullscreen mode Exit fullscreen mode
  • Configurei as bibliotecas no Views::Base (considerando que Components::Base contém as configurações do RubyUI e Phlex para rodar os componentes).
# app/views/base.rb

class Views::Base < Components::Base
  include Pagy::Frontend

  register_output_helper :lucide_icon
end
Enter fullscreen mode Exit fullscreen mode
  • Criei um componente Wrapper para setar padrões de div e adicionei a ele um específico com o formato para paginação (esse passo é opcional e pode ser adicionado direto na view que desejar).
module RubyUI
  class Wrapper < Base
    VARIANTS = %i[... pagination].freeze

    def initialize(variant: :default, **attrs)
      @variant = variant.to_sym
      super(**attrs)
    end

    def view_template(&block)
      case @variant
        (...)
      when :pagination then pagination_wrapper(&block)
      end
    end

    (...)

    def pagination_wrapper(&block)
      div(class: 'mt-6', &block)
    end

    (...)
  end
end
Enter fullscreen mode Exit fullscreen mode
  • Então criei uma partial app/views/shared/pagination.rb
# app/views/shared/pagination.rb

module Views
  module Shared
    class Pagination < Views::Base
      def initialize(pagy:, request:)
        @pagy = pagy
        @request = request
      end

      def view_template
        return if @pagy.pages <= 1

        Wrapper(variant: :pagination) do
          Pagination do
            PaginationContent do
              render_first_page
              render_prev_page
              render_pages
              render_next_page
              render_last_page
            end
          end
        end
      end

      private

      def request
        @request
      end

      def render_first_page
        return if @pagy.page == 1

        PaginationItem(href: pagy_url_for(@pagy, 1)) do
          lucide_icon('chevrons-left')
        end
      end

      def render_prev_page
        return if @pagy.prev.nil?

        PaginationItem(href: pagy_url_for(@pagy, @pagy.prev)) do
          lucide_icon('chevron-left')
        end
      end

      def render_pages
        current_page = @pagy.page
        total_pages = @pagy.pages

        pages_to_show = [
          current_page - 1,
          current_page,
          current_page + 1
        ].select { |page| page.between?(1, total_pages) }

        pages_to_show.each do |page|
          PaginationItem(href: pagy_url_for(@pagy, page), active: page == current_page) { page.to_s }
        end
      end

      def render_next_page
        return if @pagy.next.nil?

        PaginationItem(href: pagy_url_for(@pagy, @pagy.next)) do
          lucide_icon('chevron-right')
        end
      end

      def render_last_page
        return if @pagy.page == @pagy.pages

        PaginationItem(href: pagy_url_for(@pagy, @pagy.pages)) do
          lucide_icon('chevrons-right')
        end
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Ao final, a paginação aparecerá assim:

Image description

Ela é customizável, portanto, basta remover ou adicionar algo ao view_template definido. Por exemplo, se não quiser as setas de Início e Final, basta remover os renders render_last_page e render_first_page.

(...)

      def view_template
        return if @pagy.pages <= 1

        Wrapper(variant: :pagination) do
          Pagination do
            PaginationContent do
              render_prev_page
              render_pages
              render_next_page
            end
          end
        end
      end

(...)
Enter fullscreen mode Exit fullscreen mode

A visualização ficará assim:

Image description

É possível adicionar texto também, configurando os PaginationItem, como no exemplo:

(...)

      def render_prev_page
        return if @pagy.prev.nil?

        PaginationItem(href: pagy_url_for(@pagy, @pagy.prev)) do
          lucide_icon('chevron-left')
          plain "Prévia"
        end
      end

(...)
Enter fullscreen mode Exit fullscreen mode

A visualização ficará assim:

Image description


Uso

Pode configurar seguindo o padrão do pagy em seu Controller:

  # Wrap your collections with pagy in your actions
  @pagy, @records = pagy(Record.all)
Enter fullscreen mode Exit fullscreen mode

E você pode chamar a partial passando o @pagy e o request (que já vai por padrão no controller), exemplo:


render Views::Shared::Pagination.new(pagy: @pagy, request: request)

Enter fullscreen mode Exit fullscreen mode

Um controller simples ficaria assim:

class PostController < ApplicationController
  def index
    @pagy, @posts = pagy(Post.all, limit: 42)

    render Views::Shared::Pagination.new(pagy: @pagy, request: request)
  end
end
Enter fullscreen mode Exit fullscreen mode

Obrigada pela leitura! Qualquer dúvida ou módulo que achem relevante de ser adicionado, fiquem a vontade para compartilhar.
Lembrando que para ser mais concisa, não mostrei detalhes de instalação e configuração de algumas coisas necessárias, mas basta seguir os tutoriais detalhados de cada ferramenta.


Ferramentas:

Top comments (0)