From a9e5536faf3af9aa11e925b83a9f7c0db9eb36f4 Mon Sep 17 00:00:00 2001 From: Pavel Kalashnikov Date: Sat, 20 Jan 2024 15:08:53 +0400 Subject: [PATCH] Kaminari pagination default view (#42) * Use values in forms * Use form builder methods * Some refactoring * Add anyway-config * Add Tailwind-styled pagination * Add tailwind-styled pagination * Make default pagination by configuration * Add pagination config tests * Add pagination tests * Remove unused test * Update README.md * Minor updates * Remove fixing on anyway_config version * Remove empty lines in Gemfile * Ordered gems * Little fix --- .reek.yml | 3 ++ Gemfile | 12 +++--- README.md | 28 ++++++++++++ app/views/kaminari/_first_page.html.haml | 9 ++++ app/views/kaminari/_gap.html.haml | 8 ++++ app/views/kaminari/_last_page.html.haml | 9 ++++ app/views/kaminari/_next_page.html.haml | 9 ++++ app/views/kaminari/_page.html.haml | 14 ++++++ app/views/kaminari/_paginator.html.haml | 12 ++++++ app/views/kaminari/_prev_page.html.haml | 9 ++++ lib/tramway/config.rb | 16 +++---- lib/tramway/engine.rb | 36 ++++++++++++++++ .../dummy/app/controllers/users_controller.rb | 7 +++ spec/dummy/app/views/users/_user.html.haml | 4 ++ spec/dummy/app/views/users/index.html.haml | 14 ++++++ spec/dummy/config/application.rb | 2 + spec/dummy/config/initializers/tramway.rb | 5 +++ spec/dummy/config/locales/en.yml | 38 +++------------- spec/dummy/config/routes.rb | 2 - spec/pagination/render_spec.rb | 43 +++++++++++++++++++ spec/tramway/config_spec.rb | 12 +++++- spec/tramway_spec.rb | 1 - 22 files changed, 242 insertions(+), 51 deletions(-) create mode 100644 app/views/kaminari/_first_page.html.haml create mode 100644 app/views/kaminari/_gap.html.haml create mode 100644 app/views/kaminari/_last_page.html.haml create mode 100644 app/views/kaminari/_next_page.html.haml create mode 100644 app/views/kaminari/_page.html.haml create mode 100644 app/views/kaminari/_paginator.html.haml create mode 100644 app/views/kaminari/_prev_page.html.haml create mode 100644 spec/dummy/app/controllers/users_controller.rb create mode 100644 spec/dummy/app/views/users/_user.html.haml create mode 100644 spec/dummy/app/views/users/index.html.haml create mode 100644 spec/dummy/config/initializers/tramway.rb create mode 100644 spec/pagination/render_spec.rb diff --git a/.reek.yml b/.reek.yml index 698bd4c5..2ca9153d 100644 --- a/.reek.yml +++ b/.reek.yml @@ -10,6 +10,9 @@ detectors: - "Tailwinds::Form::Builder" directories: + "spec/dummy/app/controllers": + IrresponsibleModule: + enabled: false "spec/dummy/db/migrate/": FeatureEnvy: enabled: false diff --git a/Gemfile b/Gemfile index 6da74458..c360a1bf 100644 --- a/Gemfile +++ b/Gemfile @@ -6,15 +6,13 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Specify your gem's dependencies in tramway.gemspec. gemspec +gem 'anyway_config' +gem 'dry-initializer' +gem 'haml-rails' +gem 'kaminari' gem 'puma' - -gem 'sqlite3' - gem 'sprockets-rails' - -gem 'haml-rails' - -gem 'dry-initializer' +gem 'sqlite3' group :development do gem 'reek' diff --git a/README.md b/README.md index 4a858b60..5cdbf923 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Unite Ruby on Rails brilliance. Streamline development with Tramway. * [Tramway Form](https://github.com/Purple-Magic/tramway#tramway-form) * [Tramway Navbar](https://github.com/Purple-Magic/tramway#tramway-navbar) * [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms) + * [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway#tailwind-styled-pagination) ## Installation Add this line to your application's Gemfile: @@ -362,6 +363,32 @@ Available form helpers: * select * submit +### Tailwind-styled pagination for Kaminari + +Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari). + +#### How to use + +*Gemfile* +```ruby +gem 'tramway' +gem 'kaminari' +``` + +*config/initializers/tramway.rb* +```ruby +Tramway.configure do |config| + config.pagination = { enabled: true } # enabled is false by default +end +``` + +*app/views/users/index.html.haml* +```haml += paginate @users # it will render tailwind-styled pagination buttons by default +``` + +Pagination buttons looks like [this](https://play.tailwindcss.com/mqgDS5l9oY) + ## Contributing Install [lefthook](https://github.com/evilmartians/lefthook) @@ -369,6 +396,7 @@ Install [lefthook](https://github.com/evilmartians/lefthook) ``` bundle lefthook install +rspec ``` ## License diff --git a/app/views/kaminari/_first_page.html.haml b/app/views/kaminari/_first_page.html.haml new file mode 100644 index 00000000..7c6dcb77 --- /dev/null +++ b/app/views/kaminari/_first_page.html.haml @@ -0,0 +1,9 @@ +-# Link to the "First" page +-# available local variables +-# url: url to the first page +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +%span.first{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' } + = link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote diff --git a/app/views/kaminari/_gap.html.haml b/app/views/kaminari/_gap.html.haml new file mode 100644 index 00000000..3df1193c --- /dev/null +++ b/app/views/kaminari/_gap.html.haml @@ -0,0 +1,8 @@ +-# Non-link tag that stands for skipped pages... +-# available local variables +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +%span.page.gap{ class: 'px-3 py-2 text-sm font-medium text-purple-700' } + = t('views.pagination.truncate').html_safe diff --git a/app/views/kaminari/_last_page.html.haml b/app/views/kaminari/_last_page.html.haml new file mode 100644 index 00000000..371427c7 --- /dev/null +++ b/app/views/kaminari/_last_page.html.haml @@ -0,0 +1,9 @@ +-# Link to the "Last" page +-# available local variables +-# url: url to the last page +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +%span.last{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' } + = link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote diff --git a/app/views/kaminari/_next_page.html.haml b/app/views/kaminari/_next_page.html.haml new file mode 100644 index 00000000..c4c08a89 --- /dev/null +++ b/app/views/kaminari/_next_page.html.haml @@ -0,0 +1,9 @@ +-# Link to the "Next" page +-# available local variables +-# url: url to the next page +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +%span.next{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' } + = link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote diff --git a/app/views/kaminari/_page.html.haml b/app/views/kaminari/_page.html.haml new file mode 100644 index 00000000..df0a279e --- /dev/null +++ b/app/views/kaminari/_page.html.haml @@ -0,0 +1,14 @@ +-# Link showing page number +-# available local variables +-# page: a page object for "this" page +-# url: url to this page +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +- if page.current? + %span{class: "px-3 py-2 font-medium rounded-md bg-purple-500 text-white" } + = page +- else + %span{class: "cursor px-3 py-2 font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100"} + = link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} diff --git a/app/views/kaminari/_paginator.html.haml b/app/views/kaminari/_paginator.html.haml new file mode 100644 index 00000000..d683ef9b --- /dev/null +++ b/app/views/kaminari/_paginator.html.haml @@ -0,0 +1,12 @@ += paginator.render do + %nav.pagination.flex.items-center.justify-center.space-x-1 + = first_page_tag unless current_page.first? + = prev_page_tag unless current_page.first? + - each_page do |page| + - if page.display_tag? + = page_tag page + - elsif !page.was_truncated? + = gap_tag + = next_page_tag unless current_page.last? + = last_page_tag unless current_page.last? + diff --git a/app/views/kaminari/_prev_page.html.haml b/app/views/kaminari/_prev_page.html.haml new file mode 100644 index 00000000..a119611f --- /dev/null +++ b/app/views/kaminari/_prev_page.html.haml @@ -0,0 +1,9 @@ +-# Link to the "Previous" page +-# available local variables +-# url: url to the previous page +-# current_page: a page object for the currently displayed page +-# total_pages: total number of pages +-# per_page: number of items to fetch per page +-# remote: data-remote +%span.prev{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' } + = link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote diff --git a/lib/tramway/config.rb b/lib/tramway/config.rb index 25de8aa8..d802bfb5 100644 --- a/lib/tramway/config.rb +++ b/lib/tramway/config.rb @@ -1,26 +1,26 @@ # frozen_string_literal: true +require 'anyway' require 'singleton' require 'tramway/configs/entity' module Tramway # Basic configuration of Tramway # - class Config + class Config < Anyway::Config include Singleton - def initialize - @entities = [] - end + attr_config( + pagination: { enabled: false }, + entities: [] + ) def entities=(collection) - @entities = collection.map do |entity| + super(collection.map do |entity| entity_options = entity.is_a?(Hash) ? entity : { name: entity } Tramway::Configs::Entity.new(**entity_options) - end + end) end - - attr_reader :entities end end diff --git a/lib/tramway/engine.rb b/lib/tramway/engine.rb index 578dd893..f18b34d5 100644 --- a/lib/tramway/engine.rb +++ b/lib/tramway/engine.rb @@ -7,29 +7,65 @@ class Engine < ::Rails::Engine isolate_namespace Tramway initializer 'tramway.load_helpers' do + load_navbar_helper + load_views_helper + load_decorator_helper + load_form_helper + configure_pagination if Tramway.config.pagination[:enabled] + end + + private + + def load_navbar_helper ActiveSupport.on_load(:action_view) do |loaded_class| require 'tramway/helpers/navbar_helper' loaded_class.include Tramway::Helpers::NavbarHelper end + end + def load_views_helper ActiveSupport.on_load(:action_view) do |loaded_class| require 'tramway/helpers/views_helper' loaded_class.include Tramway::Helpers::ViewsHelper end + end + def load_decorator_helper ActiveSupport.on_load(:action_controller) do |loaded_class| require 'tramway/helpers/decorate_helper' loaded_class.include Tramway::Helpers::DecorateHelper end + end + def load_form_helper ActiveSupport.on_load(:action_controller) do |loaded_class| require 'tramway/helpers/form_helper' loaded_class.include Tramway::Helpers::FormHelper end end + + # :reek:NestedIterators { enabled: false } + # :reek:TooManyStatements { enabled: false } + def configure_pagination + ActiveSupport.on_load(:action_controller) do + # Detecting tramway views path + tramway_spec = Gem.loaded_specs['tramway'] + tramway_views_path = File.join(tramway_spec.full_gem_path, 'app/views') + + paths = view_paths.to_ary + + # Determine index to insert tramway views path + rails_views_index = paths.find_index { |path| path.to_s.ends_with?('app/views') } + insert_index = rails_views_index ? rails_views_index + 1 : 0 + + # Inserting tramway views path + paths.insert(insert_index, tramway_views_path) + self.view_paths = paths + end + end end end diff --git a/spec/dummy/app/controllers/users_controller.rb b/spec/dummy/app/controllers/users_controller.rb new file mode 100644 index 00000000..4bd6fbcf --- /dev/null +++ b/spec/dummy/app/controllers/users_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class UsersController < ApplicationController + def index + @users = User.all.page(params[:page]) + end +end diff --git a/spec/dummy/app/views/users/_user.html.haml b/spec/dummy/app/views/users/_user.html.haml new file mode 100644 index 00000000..254a3064 --- /dev/null +++ b/spec/dummy/app/views/users/_user.html.haml @@ -0,0 +1,4 @@ +%tr + - [:email].each do |attribute| + %td.py-2.px-4.border-b.border-r + = user.public_send(attribute) diff --git a/spec/dummy/app/views/users/index.html.haml b/spec/dummy/app/views/users/index.html.haml new file mode 100644 index 00000000..f174a2f6 --- /dev/null +++ b/spec/dummy/app/views/users/index.html.haml @@ -0,0 +1,14 @@ +.flex.justify-between.mb-4.mt-4 + %h1{ class: "text-4xl font-bold text-left text-black-700 mb-8 mt-4" } + = t('activerecord.plural.models.user.many') +%table.w-full.bg-white.border.border-gray-200 + %thead + %tr + - [:email].each do |attribute| + %th.py-2.px-4.border-b.border-r + = User.human_attribute_name attribute + %tbody#users + = render @users + +.flex.flex-end.border-t.border-gray-200.px-4.pt-4.w-full + = paginate @users diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index c5a2829a..b54ea911 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -17,6 +17,8 @@ class Application < Rails::Application # For compatibility with applications that use this config config.action_controller.include_all_helpers = false + config.hosts << 'www.example.com' + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files diff --git a/spec/dummy/config/initializers/tramway.rb b/spec/dummy/config/initializers/tramway.rb new file mode 100644 index 00000000..8e8f1c81 --- /dev/null +++ b/spec/dummy/config/initializers/tramway.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Tramway.configure do |config| + config.pagination = { enabled: true } +end diff --git a/spec/dummy/config/locales/en.yml b/spec/dummy/config/locales/en.yml index 8ca56fc7..024bc0bf 100644 --- a/spec/dummy/config/locales/en.yml +++ b/spec/dummy/config/locales/en.yml @@ -1,33 +1,7 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# The following keys must be escaped otherwise they will not be retrieved by -# the default I18n backend: -# -# true, false, on, off, yes, no -# -# Instead, surround them with single quotes. -# -# en: -# "true": "foo" -# -# To learn more, please read the Rails Internationalization guide -# available at https://guides.rubyonrails.org/i18n.html. - en: - hello: "Hello world" + views: + pagination: + first: First + last: Last + next: Next + previous: Previous diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 2cd4245f..58729c37 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true Rails.application.routes.draw do - mount Tramway::Engine => '/tramway' - resources :users resources :clients diff --git a/spec/pagination/render_spec.rb b/spec/pagination/render_spec.rb new file mode 100644 index 00000000..64d2c073 --- /dev/null +++ b/spec/pagination/render_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +shared_examples 'Click on Page' do |page_number, text = nil| + it "navigates to the correct #{text || page_number} page link is clicked" do + visit users_path + + within 'nav.pagination', match: :first do + click_link text || page_number + end + + expect(page).to have_current_path(users_path(page: page_number)) + end +end + +feature 'Order Index Page', type: %i[feature admin] do + context 'check pagination' do + before do + User.destroy_all + create_list :user, 125 + end + + it 'displays 1..5 pages links and next/last buttons' do + visit users_path + + expect(page).to have_css('span', text: '1', class: 'bg-purple-500') + + (2..5).each do |i| + expect(page).to have_link(i.to_s, href: users_path(page: i)) + end + + expect(page).to have_link('Next', href: users_path(page: 2)) + + expect(page).to have_link('Last', href: users_path(page: 5)) + end + + include_examples 'Click on Page', '2' + include_examples 'Click on Page', '3' + include_examples 'Click on Page', '4' + include_examples 'Click on Page', '5' + include_examples 'Click on Page', '2', 'Next' + include_examples 'Click on Page', '5', 'Last' + end +end diff --git a/spec/tramway/config_spec.rb b/spec/tramway/config_spec.rb index 176ca0f2..7eb732ae 100644 --- a/spec/tramway/config_spec.rb +++ b/spec/tramway/config_spec.rb @@ -22,7 +22,7 @@ end end - describe '#entities' do + describe 'entities' do context 'with empty collection' do before do config.entities = [] @@ -45,4 +45,14 @@ end end end + + describe 'pagination' do + context 'change default value' do + before { config.pagination = { enabled: true } } + + it 'returns updated pagination enabled value' do + expect(config.pagination[:enabled]).to be_truthy + end + end + end end diff --git a/spec/tramway_spec.rb b/spec/tramway_spec.rb index 0e86c7c0..c98d5249 100644 --- a/spec/tramway_spec.rb +++ b/spec/tramway_spec.rb @@ -19,7 +19,6 @@ describe '.config' do it 'returns the Tramway::Config instance' do - allow(Tramway::Config).to receive(:instance).and_return(config) expect(Tramway.config).to eq(config) end end