Royce  Reinger

Royce Reinger

1659676740

Kaminari: A Scope & Engine Based, Clean, Powerful, Customizable

Kaminari 

A Scope & Engine based, clean, powerful, customizable and sophisticated paginator for modern web app frameworks and ORMs

Features

Clean

Does not globally pollute Array, Hash, Object or AR::Base.

Easy to Use

Just bundle the gem, then your models are ready to be paginated. No configuration required. Don't have to define anything in your models or helpers.

Simple Scope-based API

Everything is method chainable with less "Hasheritis". You know, that's the modern Rails way. No special collection class or anything for the paginated values, instead using a general AR::Relation instance. So, of course you can chain any other conditions before or after the paginator scope.

Customizable Engine-based I18n-aware Helpers

As the whole pagination helper is basically just a collection of links and non-links, Kaminari renders each of them through its own partial template inside the Engine. So, you can easily modify their behaviour, style or whatever by overriding partial templates.

ORM & Template Engine Agnostic

Kaminari supports multiple ORMs (ActiveRecord, DataMapper, Mongoid, MongoMapper) multiple web frameworks (Rails, Sinatra, Grape), and multiple template engines (ERB, Haml, Slim).

Modern

The pagination helper outputs the HTML5 <nav> tag by default. Plus, the helper supports Rails unobtrusive Ajax.

Supported Versions

Ruby 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2

Rails 4.1, 4.2, 5.0, 5.1, 5.2, 6.0, 6.1, 7.0, 7.1

Sinatra 1.4, 2.0

Haml 3+

Mongoid 3+

MongoMapper 0.9+

DataMapper 1.1.0+

Installation

To install kaminari on the default Rails stack, just put this line in your Gemfile:

gem 'kaminari'

Then bundle:

% bundle

If you're building non-Rails of non-ActiveRecord app and want the pagination feature on it, please take a look at Other Framework/Library Support section.

Query Basics

The page Scope

To fetch the 7th page of users (default per_page is 25)

User.page(7)

Note: pagination starts at page 1, not at page 0 (page(0) will return the same results as page(1)).

Kaminari does not add an order to queries. To avoid surprises, you should generally include an order in paginated queries. For example:

User.order(:name).page(7)

You can get page numbers or page conditions by using below methods.

User.count                     #=> 1000
User.page(1).limit_value       #=> 20
User.page(1).total_pages       #=> 50
User.page(1).current_page      #=> 1
User.page(1).next_page         #=> 2
User.page(2).prev_page         #=> 1
User.page(1).first_page?       #=> true
User.page(50).last_page?       #=> true
User.page(100).out_of_range?   #=> true

The per Scope

To show a lot more users per each page (change the per value)

User.order(:name).page(7).per(50)

Note that the per scope is not directly defined on the models but is just a method defined on the page scope. This is absolutely reasonable because you will never actually use per without specifying the page number.

Keep in mind that per internally utilizes limit and so it will override any limit that was set previously. And if you want to get the size for all request records you can use total_count method:

User.count                     #=> 1000
a = User.limit(5); a.count     #=> 5
a.page(1).per(20).size         #=> 20
a.page(1).per(20).total_count  #=> 1000

The padding Scope

Occasionally you need to pad a number of records that is not a multiple of the page size.

User.order(:name).page(7).per(50).padding(3)

Note that the padding scope also is not directly defined on the models.

Unscoping

If for some reason you need to unscope page and per methods you can call except(:limit, :offset)

users = User.order(:name).page(7).per(50)
unpaged_users = users.except(:limit, :offset) # unpaged_users will not use the kaminari scopes

Configuring Kaminari

General Configuration Options

You can configure the following default values by overriding these values using Kaminari.configure method.

default_per_page      # 25 by default
max_per_page          # nil by default
max_pages             # nil by default
window                # 4 by default
outer_window          # 0 by default
left                  # 0 by default
right                 # 0 by default
page_method_name      # :page by default
param_name            # :page by default
params_on_first_page  # false by default

There's a handy generator that generates the default configuration file into config/initializers directory. Run the following generator command, then edit the generated file.

% rails g kaminari:config

Changing page_method_name

You can change the method name page to bonzo or plant or whatever you like, in order to play nice with existing page method or association or scope or any other plugin that defines page method on your models.

Configuring Default per_page Value for Each Model by paginates_per

You can specify default per_page value per each model using the following declarative DSL.

class User < ActiveRecord::Base
  paginates_per 50
end

Configuring Max per_page Value for Each Model by max_paginates_per

You can specify max per_page value per each model using the following declarative DSL. If the variable that specified via per scope is more than this variable, max_paginates_per is used instead of it. Default value is nil, which means you are not imposing any max per_page value.

class User < ActiveRecord::Base
  max_paginates_per 100
end

Configuring max_pages Value for Each Model by max_pages

You can specify max_pages value per each model using the following declarative DSL. This value restricts the total number of pages that can be returned. Useful for setting limits on large collections.

class User < ActiveRecord::Base
  max_pages 100
end

Configuring params_on_first_page when using ransack_memory

If you are using the ransack_memory gem and experience problems navigating back to the previous or first page, set the params_on_first_page setting to true.

Controllers

The Page Parameter Is in params[:page]

Typically, your controller code will look like this:

@users = User.order(:name).page params[:page]

Views

The Same Old Helper Method

Just call the paginate helper:

<%= paginate @users %>

This will render several ?page=N pagination links surrounded by an HTML5 <nav> tag.

Helpers

The paginate Helper Method

<%= paginate @users %>

This would output several pagination links such as « First ‹ Prev ... 2 3 4 5 6 7 8 9 10 ... Next › Last »

Specifying the "inner window" Size (4 by default)

<%= paginate @users, window: 2 %>

This would output something like ... 5 6 7 8 9 ... when 7 is the current page.

Specifying the "outer window" Size (0 by default)

<%= paginate @users, outer_window: 3 %>

This would output something like 1 2 3 ...(snip)... 18 19 20 while having 20 pages in total.

Outer Window Can Be Separately Specified by left, right (0 by default)

<%= paginate @users, left: 1, right: 3 %>

This would output something like 1 ...(snip)... 18 19 20 while having 20 pages in total.

Changing the Parameter Name (:param_name) for the Links

<%= paginate @users, param_name: :pagina %>

This would modify the query parameter name on each links.

Extra Parameters (:params) for the Links

<%= paginate @users, params: {controller: 'foo', action: 'bar', format: :turbo_stream} %>

This would modify each link's url_option. :controller and :action might be the keys in common.

Ajax Links (crazy simple, but works perfectly!)

<%= paginate @users, remote: true %>

This would add data-remote="true" to all the links inside.

Specifying an Alternative Views Directory (default is kaminari/)

<%= paginate @users, views_prefix: 'templates' %>

This would search for partials in app/views/templates/kaminari. This option makes it easier to do things like A/B testing pagination templates/themes, using new/old templates at the same time as well as better integration with other gems such as cells.

The link_to_next_page and link_to_previous_page (aliased to link_to_prev_page) Helper Methods

<%= link_to_next_page @items, 'Next Page' %>

This simply renders a link to the next page. This would be helpful for creating a Twitter-like pagination feature.

The helper methods support a params option to further specify the link. If format needs to be set, include it in the params hash.

<%= link_to_next_page @items, 'Next Page', params: {controller: 'foo', action: 'bar', format: :turbo_stream} %>

The page_entries_info Helper Method

<%= page_entries_info @posts %>

This renders a helpful message with numbers of displayed vs. total entries.

By default, the message will use the humanized class name of objects in collection: for instance, "project types" for ProjectType models. The namespace will be cut out and only the last name will be used. Override this with the :entry_name parameter:

<%= page_entries_info @posts, entry_name: 'item' %>
#=> Displaying items 6 - 10 of 26 in total

The rel_next_prev_link_tags Helper Method

<%= rel_next_prev_link_tags @users %>

This renders the rel next and prev link tags for the head.

The path_to_next_page Helper Method

<%= path_to_next_page @users %>

This returns the server relative path to the next page.

The path_to_prev_page Helper Method

<%= path_to_prev_page @users %>

This returns the server relative path to the previous page.

I18n and Labels

The default labels for 'first', 'last', 'previous', '...' and 'next' are stored in the I18n yaml inside the engine, and rendered through I18n API. You can switch the label value per I18n.locale for your internationalized application. Keys and the default values are the following. You can override them by adding to a YAML file in your Rails.root/config/locales directory.

en:
  views:
    pagination:
      first: "&laquo; First"
      last: "Last &raquo;"
      previous: "&lsaquo; Prev"
      next: "Next &rsaquo;"
      truncate: "&hellip;"
  helpers:
    page_entries_info:
      one_page:
        display_entries:
          zero: "No %{entry_name} found"
          one: "Displaying <b>1</b> %{entry_name}"
          other: "Displaying <b>all %{count}</b> %{entry_name}"
      more_pages:
        display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"

If you use non-English localization see i18n rules for changing one_page:display_entries block.

Customizing the Pagination Helper

Kaminari includes a handy template generator.

To Edit Your Paginator

Run the generator first,

% rails g kaminari:views default

then edit the partials in your app's app/views/kaminari/ directory.

For Haml/Slim Users

You can use the html2haml gem or the html2slim gem to convert erb templates. The kaminari gem will automatically pick up haml/slim templates if you place them in app/views/kaminari/.

Multiple Templates

In case you need different templates for your paginator (for example public and admin), you can pass --views-prefix directory like this:

% rails g kaminari:views default --views-prefix admin

that will generate partials in app/views/admin/kaminari/ directory.

Themes

The generator has the ability to fetch several sample template themes from the external repository (https://github.com/amatsuda/kaminari_themes) in addition to the bundled "default" one, which will help you creating a nice looking paginator.

% rails g kaminari:views THEME

To see the full list of available themes, take a look at the themes repository, or just hit the generator without specifying THEME argument.

% rails g kaminari:views

Multiple Themes

To utilize multiple themes from within a single application, create a directory within the app/views/kaminari/ and move your custom template files into that directory.

% rails g kaminari:views default (skip if you have existing kaminari views)
% cd app/views/kaminari
% mkdir my_custom_theme
% cp _*.html.* my_custom_theme/

Next, reference that directory when calling the paginate method:

<%= paginate @users, theme: 'my_custom_theme' %>

Customize away!

Note: if the theme isn't present or none is specified, kaminari will default back to the views included within the gem.

Paginating Without Issuing SELECT COUNT Query

Generally the paginator needs to know the total number of records to display the links, but sometimes we don't need the total number of records and just need the "previous page" and "next page" links. For such use case, Kaminari provides without_count mode that creates a paginatable collection without counting the number of all records. This may be helpful when you're dealing with a very large dataset because counting on a big table tends to become slow on RDBMS.

Just add .without_count to your paginated object:

User.page(3).without_count

In your view file, you can only use simple helpers like the following instead of the full-featured paginate helper:

<%= link_to_prev_page @users, 'Previous Page' %>
<%= link_to_next_page @users, 'Next Page' %>

Paginating a Generic Array object

Kaminari provides an Array wrapper class that adapts a generic Array object to the paginate view helper. However, the paginate helper doesn't automatically handle your Array object (this is intentional and by design). Kaminari::paginate_array method converts your Array object into a paginatable Array that accepts page method.

@paginatable_array = Kaminari.paginate_array(my_array_object).page(params[:page]).per(10)

You can specify the total_count value through options Hash. This would be helpful when handling an Array-ish object that has a different count value from actual count such as RSolr search result or when you need to generate a custom pagination. For example:

@paginatable_array = Kaminari.paginate_array([], total_count: 145).page(params[:page]).per(10)

or, in the case of using an external API to source the page of data:

page_size = 10
one_page = get_page_of_data params[:page], page_size
@paginatable_array = Kaminari.paginate_array(one_page.data, total_count: one_page.total_count).page(params[:page]).per(page_size)

Creating Friendly URLs and Caching

Because of the page parameter and Rails routing, you can easily generate SEO and user-friendly URLs. For any resource you'd like to paginate, just add the following to your routes.rb:

resources :my_resources do
  get 'page/:page', action: :index, on: :collection
end

If you are using Rails 4 or later, you can simplify route definitions by using concern:

concern :paginatable do
  get '(page/:page)', action: :index, on: :collection, as: ''
end

resources :my_resources, concerns: :paginatable

This will create URLs like /my_resources/page/33 instead of /my_resources?page=33. This is now a friendly URL, but it also has other added benefits...

Because the page parameter is now a URL segment, we can leverage on Rails page caching!

NOTE: In this example, I've pointed the route to my :index action. You may have defined a custom pagination action in your controller - you should point action: :your_custom_action instead.

Other Framework/Library Support

The kaminari gem

Technically, the kaminari gem consists of 3 individual components:

kaminari-core: the core pagination logic
kaminari-activerecord: Active Record adapter
kaminari-actionview: Action View adapter

So, bundling gem 'kaminari' is equivalent to the following 2 lines (kaminari-core is referenced from the adapters):

gem 'kaminari-activerecord'
gem 'kaminari-actionview'

For Other ORM Users

If you want to use other supported ORMs instead of ActiveRecord, for example Mongoid, bundle its adapter instead of kaminari-activerecord.

gem 'kaminari-mongoid'
gem 'kaminari-actionview'

Kaminari currently provides adapters for the following ORMs:

For Other Web Framework Users

If you want to use other web frameworks instead of Rails + Action View, for example Sinatra, bundle its adapter instead of kaminari-actionview.

gem 'kaminari-activerecord'
gem 'kaminari-sinatra'

Kaminari currently provides adapters for the following web frameworks:

For More Information

Check out Kaminari recipes on the GitHub Wiki for more advanced tips and techniques. https://github.com/kaminari/kaminari/wiki/Kaminari-recipes

Questions, Feedback

Feel free to message me on Github (amatsuda) or Twitter (@a_matsuda) ☇☇☇ :)

Contributing to Kaminari

Fork, fix, then send a pull request.

To run the test suite locally against all supported frameworks:

% bundle install
% rake test:all

To target the test suite against one framework:

% rake test:active_record_50

You can find a list of supported test tasks by running rake -T. You may also find it useful to run a specific test for a specific framework. To do so, you'll have to first make sure you have bundled everything for that configuration, then you can run the specific test:

% BUNDLE_GEMFILE='gemfiles/active_record_50.gemfile' bundle install
% BUNDLE_GEMFILE='gemfiles/active_record_50.gemfile' TEST=kaminari-core/test/requests/navigation_test.rb bundle exec rake test

Author: Kaminari
Source Code: https://github.com/kaminari/kaminari 
License: MIT license

#ruby #rails #pagination 

Kaminari: A Scope & Engine Based, Clean, Powerful, Customizable

Build JSON API-compliant APIs on Rails with No Learning Curve

JSONAPI::Utils  

Simple yet powerful way to get your Rails API compliant with JSON API.

JSONAPI::Utils (JU) is built on top of JSONAPI::Resources taking advantage of its resource-driven style and bringing a set of helpers to easily build modern JSON APIs with no or less learning curve.

After installing the gem and defining the resources/routes, it's as simple as calling a render helper:

class UsersController < ActionController::Base
  include JSONAPI::Utils

  def index
    jsonapi_render json: User.all
  end
end

Installation

Support:

  • Ruby 1.9+ with Rails 4
  • Ruby 2.4+ with Rails 5

For Rails 4 add this to your application's Gemfile:

gem 'jsonapi-utils', '~> 0.4.9'

For Rails 5+:

gem 'jsonapi-utils', '~> 0.7.3'

And then execute:

$ bundle

Why JSONAPI::Utils?

One of the main motivations behind JSONAPI::Utils is to keep things explicit in controllers (no hidden actions :-) so that developers can easily understand and maintain their code.

Unlike JSONAPI::Resources (JR), JU doesn't care about how you will operate your controller actions. The gem deals only with the request validation and response rendering (via JR's objects) and provides a set of helpers (renders, formatters etc) along the way. Thus developers can decide how to actually operate their actions: service objects, interactors etc.

Usage

Response

Renders

JU brings two main renders to the game, working pretty much the same way as Rails' ActionController#render method:

  • jsonapi_render
  • jsonapi_render_errors

jsonapi_render

It renders a JSON API-compliant response.

# app/controllers/users_controller.rb
# GET /users
def index
  jsonapi_render json: User.all
end

# GET /users/:id
def show
  jsonapi_render json: User.find(params[:id])
end

Arguments:

  • json: object to be rendered as a JSON document: ActiveRecord object, Hash or Array;
  • status: HTTP status code (Integer, String or Symbol). If ommited a status code will be automatically infered;
  • options:
    • resource: explicitly points the resource to be used in the serialization. By default, JU will select resources by inferencing from controller's name.
    • count: explicitly points the total count of records for the request in order to build a proper pagination. By default, JU will count the total number of records.
    • model: sets the model reference in cases when json is a Hash or a collection of Hashes.

Other examples:

# Specify a particular HTTP status code
jsonapi_render json: new_user, status: :created

# Forcing a different resource
jsonapi_render json: User.all, options: { resource: V2::UserResource }

# Using a specific count
jsonapi_render json: User.some_weird_scope, options: { count: User.some_weird_scope_count }

# Hash rendering
jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model: User }

# Collection of Hashes rendering
jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }

jsonapi_render_errors

It renders a JSON API-compliant error response.

# app/controllers/users_controller.rb
# POST /users
  def create
    user = User.new(user_params)
    if user.save
      jsonapi_render json: user, status: :created
    else
      jsonapi_render_errors json: user, status: :unprocessable_entity
    end
  end

Arguments:

  • Exception
  • json: object to be rendered as a JSON document: ActiveRecord, Exception, Array or any object which implements the errors method;
  • status: HTTP status code (Integer, String or Symbol). If ommited a status code will be automatically infered from the error body.

Other examples:

# Render errors from a custom exception:
jsonapi_render_errors Exceptions::MyCustomError.new(user)

# Render errors from an Array<Hash>:
errors = [{ id: 'validation', title: 'Something went wrong', code: '100' }]
jsonapi_render_errors json: errors, status: :unprocessable_entity

Formatters

In the backstage these are the guys which actually parse the ActiveRecord/Hash object to build a new Hash compliant with JSON API's specs. Formatters can be called anywhere in controllers being very useful if you need to do some work with the response's body before rendering the actual response.

Note: the resulting Hash from those methods can not be passed as argument to JSONAPI::Utils#jsonapi_render or JSONAPI::Utils#jsonapi_render_error, instead it needs to be rendered by the usual ActionController#render.

jsonapi_format

Because of semantic reasons JSONAPI::Utils#jsonapi_serialize was renamed to JSONAPI::Utils#jsonapi_format.

# app/controllers/users_controller.rb
def index
  body = jsonapi_format(User.all)
  render json: do_some_magic_with(body)
end

Arguments:

  • First: ActiveRecord object, Hash or Array;
  • Last: Hash of options (same as JSONAPI::Utils#jsonapi_render).

Paginators

Pagination works out of the box on JU, you just need to decide which kind of paginator you'd like to use.

It's really easy to work with pagination on JU, actually it's just a matter of chosing the paginator you wish in your JR's config file:

# config/initializers/jsonapi_resources.rb
JSONAPI.configure do |config|
  # :none, :offset, :paged, or a custom paginator name
  config.default_paginator = :paged

  # Output pagination links at top level
  config.top_level_links_include_pagination = true
  
  # Default sizes
  config.default_page_size = 70
  config.maximum_page_size = 100
end

As you may have noticed above, it's possible to use custom paginators. In order to create your own paginator your just need to define a class which inherits from JSONAPI::Paginator and implements the #pagination_range method which in turn must return the range to be applied over the resulting collection.

For example, if you would like to paginate over a collection of hashes, you may implement the #pagination_range method as below:

class CustomPaginator < JSONAPI::Paginator
  def pagination_range(page_params)
    offset = page_params['offset']
    limit  = JSONAPI.configuration.default_page_size
    offset..offset + limit - 1 # resulting range
  end

And then it can be either set at the resource class level (e.g. UserResource.paginator :custom) or via config initializer:

# config/initializers/jsonapi_resources.rb
JSONAPI.configure do |config|
  config.default_paginator = :custom
end

Request

Before a controller action gets executed, JSONAPI::Utils will validate the request against JSON API's specs as well as evaluating the eventual query string params to check if they match the resource's definition. If something goes wrong during the validation process, JU will render an error response like this examples below:

HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json

{
  "errors": [
    {
      "title": "Invalid resource",
      "detail": "foo is not a valid resource.",
      "code": "101",
      "status": "400"
    },
    {
      "title": "Invalid resource",
      "detail": "foobar is not a valid resource.",
      "code": "101",
      "status": "400"
    },
    {
      "title": "Invalid field",
      "detail": "bar is not a valid relationship of users",
      "code": "112",
      "status": "400"
    }
  ]
}

Params helpers

JU brings helper methods as a shortcut to get values from permitted params based on the resource's configuration.

  • resource_params:
    • Returns the permitted params present in the attributes JSON member;
      • Example: { name: 'Bilbo', gender: 'male', city: 'Shire' }
    • Same of calling: params.require(:data).require(:attributes).permit(:name, :gender, :city)
  • relationship_params:
    • Returns the relationship ids, distinguished by key, present in relationships JSON member;
      • Example: { author: 1, posts: [1, 2, 3] }
    • Same as calling: params.require(:relationships).require(:author).require(:data).permit(:id)

Full example

After installing the gem you simply need to:

  1. Include the gem's module (include JSONAPI::Utils) in a controller (eg. BaseController);
  2. Define the resources which will be exposed via REST API;
  3. Define the application's routes;
  4. Use JSONAPI Utils' helper methods (eg. renders, formatters, params helpers etc).

Ok, now it's time to start our complete example. So, let's say we have a Rails application for a super simple blog:

Models

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :posts
  validates :first_name, :last_name, presence: true
end

# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :author, class_name: 'User', foreign_key: 'user_id'
  validates :title, :body, presence: true
end

Resources

Here is where we define how our models are exposed as resources on the API:

# app/resources/user_resource.rb
class UserResource < JSONAPI::Resource
  attributes :first_name, :last_name, :full_name, :birthday

  has_many :posts

  def full_name
    "#{@model.first_name} #{@model.last_name}"
  end
end

# app/resources/post_resource.rb
class PostResource < JSONAPI::Resource
  attributes :title, :body
  has_one :author
end

Routes & Controllers

Let's define the routes using the jsonapi_resources method provided by JR:

Rails.application.routes.draw do
  jsonapi_resources :users do
    jsonapi_resources :posts
  end
end

In controllers we just need to include the JSONAPI::Utils module.

Note: some default rendering can be set like the below example where jsonapi_render_not_found is used when a record is not found in the database.

# app/controllers/base_controller.rb
class BaseController < ActionController::Base
  include JSONAPI::Utils
  protect_from_forgery with: :null_session
  rescue_from ActiveRecord::RecordNotFound, with: :jsonapi_render_not_found
end

With the helper methods inhirited from JSONAPI::Utils in our BaseController, now it's all about to write our actions like the following:

# app/controllers/users_controller.rb
class UsersController < BaseController
  # GET /users
  def index
    users = User.all
    jsonapi_render json: users
  end

  # GET /users/:id
  def show
    user = User.find(params[:id])
    jsonapi_render json: user
  end

  # POST /users
  def create
    user = User.new(resource_params)
    if user.save
      jsonapi_render json: user, status: :created
    else
      jsonapi_render_errors json: user, status: :unprocessable_entity
    end
  end

  # PATCH /users/:id
  def update
    user = User.find(params[:id])
    if user.update(resource_params)
      jsonapi_render json: user
    else
      jsonapi_render_errors json: user, status: :unprocessable_entity
    end
  end

  # DELETE /users/:id
  def destroy
    User.find(params[:id]).destroy
    head :no_content
  end
end

And:

# app/controllers/posts_controller.rb
class PostsController < BaseController
  before_action :load_user, except: :create

  # GET /users/:user_id/posts
  def index
    jsonapi_render json: @user.posts, options: { count: 100 }
  end

  # GET /users/:user_id/posts/:id
  def show
    jsonapi_render json: @user.posts.find(params[:id])
  end

  # POST /posts
  def create
    post = Post.new(post_params)
    if post.save
      jsonapi_render json: post, status: :created
    else
      jsonapi_render_errors json: post, status: :unprocessable_entity
    end
  end

  private

  def post_params
    resource_params.merge(user_id: relationship_params[:author])
  end

  def load_user
    @user = User.find(params[:user_id])
  end
end

Initializer

In order to enable a proper pagination, record count etc, an initializer could be defined such as:

# config/initializers/jsonapi_resources.rb
JSONAPI.configure do |config|
  config.json_key_format = :underscored_key
  config.route_format = :dasherized_route

  config.allow_include = true
  config.allow_sort = true
  config.allow_filter = true

  config.raise_if_parameters_not_allowed = true

  config.default_paginator = :paged

  config.top_level_links_include_pagination = true

  config.default_page_size = 10
  config.maximum_page_size = 20

  config.top_level_meta_include_record_count = true
  config.top_level_meta_record_count_key = :record_count

  config.top_level_meta_include_page_count = true
  config.top_level_meta_page_count_key = :page_count

  config.use_text_errors = false

  config.exception_class_whitelist = []

  config.always_include_to_one_linkage_data = false
end

You may want a different configuration for your API. For more information check this.

Requests & Responses

Here are examples of requests – based on those sample controllers – and their respective JSON responses.

Index

Request:

GET /users HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "type": "users",
      "links": {
        "self": "http://api.myblog.com/users/1"
      },
      "attributes": {
        "first_name": "Tiago",
        "last_name": "Guedes",
        "full_name": "Tiago Guedes",
        "birthday": null
      },
      "relationships": {
        "posts": {
          "links": {
            "self": "http://api.myblog.com/users/1/relationships/posts",
            "related": "http://api.myblog.com/users/1/posts"
          }
        }
      }
    },
    {
      "id": "2",
      "type": "users",
      "links": {
        "self": "http://api.myblog.com/users/2"
      },
      "attributes": {
        "first_name": "Douglas",
        "last_name": "André",
        "full_name": "Douglas André",
        "birthday": null
      },
      "relationships": {
        "posts": {
          "links": {
            "self": "http://api.myblog.com/users/2/relationships/posts",
            "related": "http://api.myblog.com/users/2/posts"
          }
        }
      }
    }
  ],
  "meta": {
    "record_count": 2
  },
  "links": {
    "first": "http://api.myblog.com/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
    "last": "http://api.myblog.com/users?page%5Bnumber%5D=1&page%5Bsize%5D=10"
  }
}

Index (options)

Request:

GET /users?include=posts&fields[users]=first_name,last_name,posts&fields[posts]=title&sort=first_name,last_name&page[number]=1&page[size]=1 HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "2",
      "type": "users",
      "links": {
        "self": "http://api.myblog.com/users/2"
      },
      "attributes": {
        "first_name": "Douglas",
        "last_name": "André"
      },
      "relationships": {
        "posts": {
          "links": {
            "self": "http://api.myblog.com/users/2/relationships/posts",
            "related": "http://api.myblog.com/users/2/posts"
          },
          "data": []
        }
      }
    },
    {
      "id": "1",
      "type": "users",
      "links": {
        "self": "http://api.myblog.com/users/1"
      },
      "attributes": {
        "first_name": "Tiago",
        "last_name": "Guedes"
      },
      "relationships": {
        "posts": {
          "links": {
            "self": "http://api.myblog.com/users/1/relationships/posts",
            "related": "http://api.myblog.com/users/1/posts"
          },
          "data": [
            {
              "type": "posts",
              "id": "1"
            }
          ]
        }
      }
    }
  ],
  "included": [
    {
      "id": "1",
      "type": "posts",
      "links": {
        "self": "http://api.myblog.com/posts/1"
      },
      "attributes": {
        "title": "An awesome post"
      }
    }
  ],
  "meta": {
    "record_count": 2
  },
  "links": {
    "first": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name",
    "last": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name"
  }
}

Show

Request:

GET /users/1 HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "type": "users",
    "links": {
      "self": "http://api.myblog.com/users/1"
    },
    "attributes": {
      "first_name": "Tiago",
      "last_name": "Guedes",
      "full_name": "Tiago Guedes",
      "birthday": null
    },
    "relationships": {
      "posts": {
        "links": {
          "self": "http://api.myblog.com/users/1/relationships/posts",
          "related": "http://api.myblog.com/users/1/posts"
        }
      }
    }
  }
}

Show (options)

Request:

GET /users/1?include=posts&fields[users]=full_name,posts&fields[posts]=title HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "type": "users",
    "links": {
      "self": "http://api.myblog.com/users/1"
    },
    "attributes": {
      "full_name": "Tiago Guedes"
    },
    "relationships": {
      "posts": {
        "links": {
          "self": "http://api.myblog.com/users/1/relationships/posts",
          "related": "http://api.myblog.com/users/1/posts"
        },
        "data": [
          {
            "type": "posts",
            "id": "1"
          }
        ]
      }
    }
  },
  "included": [
    {
      "id": "1",
      "type": "posts",
      "links": {
        "self": "http://api.myblog.com/posts/1"
      },
      "attributes": {
        "title": "An awesome post"
      }
    }
  ]
}

Relationships (identifier objects)

Request:

GET /users/1/relationships/posts HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "links": {
    "self": "http://api.myblog.com/users/1/relationships/posts",
    "related": "http://api.myblog.com/users/1/posts"
  },
  "data": [
    {
      "type": "posts",
      "id": "1"
    }
  ]
}

Nested resources

Request:

GET /users/1/posts HTTP/1.1
Accept: application/vnd.api+json

Response:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "type": "posts",
      "links": {
        "self": "http://api.myblog.com/posts/1"
      },
      "attributes": {
        "title": "An awesome post",
        "body": "Lorem ipsum dolot sit amet"
      },
      "relationships": {
        "author": {
          "links": {
            "self": "http://api.myblog.com/posts/1/relationships/author",
            "related": "http://api.myblog.com/posts/1/author"
          }
        }
      }
    }
  ],
  "meta": {
    "record_count": 1
  },
  "links": {
    "first": "http://api.myblog.com/posts?page%5Bnumber%5D=1&page%5Bsize%5D=10",
    "last": "http://api.myblog.com/posts?page%5Bnumber%5D=1&page%5Bsize%5D=10"
  }
}

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake rspec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/tiagopog/jsonapi-utils. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.


Author: tiagopog
Source code: https://github.com/tiagopog/jsonapi-utils
License: MIT license

#ruby #ruby-on-rails 
 

Build JSON API-compliant APIs on Rails with No Learning Curve

Cakephp-api-pagination: CakePHP API Pagination

CakePHP API Pagination

This is a simple component for CakePHP 4 which injects pagination information from CakePHP's Paginator into serialized JsonView and XmlView responses.

Install

Via Composer

$ composer require bcrowe/cakephp-api-pagination

Load the plugin by adding $this->addPlugin('BryanCrowe/ApiPagination'); to the bootsrap method in your project’s src/Application.php:

public function bootstrap(): void
{
    parent::bootstrap();
    
    // ... bootstrap code ...

    // load more plugins here
    
    $this->addPlugin('BryanCrowe/ApiPagination');
}

Usage

Make sure your application has been set up to use data views; see the Enabling Data Views in Your Application section of the CakePHP documentation.

Then, load ApiPaginationComponent:

$this->loadComponent('BryanCrowe/ApiPagination.ApiPagination');

Then, go ahead and set your paginated view variable like so:

$this->set('articles', $this->paginate($this->Articles));
$this->viewBuilder()->setOption('serialize', ['articles']);

Note: It is important that your serialize option is an array, e.g. ['articles'], so that your pagination information can be set under its own pagination key.

Your JsonView and XmlView responses will now contain the pagination information, and will look something like this:

{
    "articles": ["...", "...", "..."],
    "pagination": {
        "finder": "all",
        "page": 1,
        "current": 20,
        "count": 5000,
        "perPage": 20,
        "prevPage": false,
        "nextPage": true,
        "pageCount": 250,
        "sort": null,
        "direction": false,
        "limit": null,
        "sortDefault": false,
        "directionDefault": false
    }
}

Configuring the Pagination Output

ApiPagination has four keys for configuration: key, aliases, visible and model.

key allows you to change the name of the pagination key.

aliases allows you to change names of the pagination detail keys.

visible allows you to set which pagination keys will be exposed in the response. Note: Whenever setting a key's visibility, make sure to use the aliased name if you've given it one.

model allows you to set the name of the model the pagination is applied on if the controller does not follow CakePHP conventions, e.g. ArticlesIndexController. Per default the model is the name of the controller, e.g. Articles for ArticlesController.

An example using all these configuration keys:

$this->loadComponent('BryanCrowe/ApiPagination.ApiPagination', [
    'key' => 'paging',
    'aliases' => [
        'page' => 'currentPage',
        'current' => 'resultCount'
    ],
    'visible' => [
        'currentPage',
        'resultCount',
        'prevPage',
        'nextPage'
    ],
    'model' => 'Articles',
]);

This configuration would yield:

{
    "articles": ["...", "...", "..."],
    "paging": {
        "prevPage": false,
        "nextPage": true,
        "currentPage": 1,
        "resultCount": 20
    }
}

Changelog

Please see CHANGELOG for more information what has changed recently.

Testing

$ composer test

Contributing

Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email bryan@bryan-crowe.com instead of using the issue tracker.

Credits

Author: Bcrowe
Source Code: https://github.com/bcrowe/cakephp-api-pagination 
License: MIT license

#php #cakephp #api #pagination 

Cakephp-api-pagination: CakePHP API Pagination

Lampager-laravel: Rapid Pagination for Laravel

Lampager for Laravel

Rapid pagination without using OFFSET

Requirements

Installing

composer require lampager/lampager-laravel

Basic Usage

Register service provider.

config/app.php:

        /*
         * Package Service Providers...
         */
        Lampager\Laravel\MacroServiceProvider::class,

Then you can chain ->lampager() method from Query Builder, Eloquent Builder and Relation.

$cursor = [
    'id' => 3,
    'created_at' => '2017-01-10 00:00:00',
    'updated_at' => '2017-01-20 00:00:00',
];

$result = App\Post::whereUserId(1)
    ->lampager()
    ->forward()
    ->limit(5)
    ->orderByDesc('updated_at') // ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC
    ->orderByDesc('created_at')
    ->orderByDesc('id')
    ->seekable()
    ->paginate($cursor)
    ->toJson(JSON_PRETTY_PRINT);

It will run the optimized query.

(

    SELECT * FROM `posts`
    WHERE `user_id` = 1
    AND (
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` > 3
        OR
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` > '2017-01-10 00:00:00'
        OR
        `updated_at` > '2017-01-20 00:00:00'
    )
    ORDER BY `updated_at` ASC, `created_at` ASC, `id` ASC
    LIMIT 1

) UNION ALL (

    SELECT * FROM `posts`
    WHERE `user_id` = 1
    AND (
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` <= 3
        OR
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` < '2017-01-10 00:00:00'
        OR
        `updated_at` < '2017-01-20 00:00:00'
    )
    ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC
    LIMIT 6

)

And you'll get

{
  "records": [
    {
      "id": 3,
      "user_id": 1,
      "text": "foo",
      "created_at": "2017-01-10 00:00:00",
      "updated_at": "2017-01-20 00:00:00"
    },
    {
      "id": 5,
      "user_id": 1,
      "text": "bar",
      "created_at": "2017-01-05 00:00:00",
      "updated_at": "2017-01-20 00:00:00"
    },
    {
      "id": 4,
      "user_id": 1,
      "text": "baz",
      "created_at": "2017-01-05 00:00:00",
      "updated_at": "2017-01-20 00:00:00"
    },
    {
      "id": 2,
      "user_id": 1,
      "text": "qux",
      "created_at": "2017-01-17 00:00:00",
      "updated_at": "2017-01-18 00:00:00"
    },
    {
      "id": 1,
      "user_id": 1,
      "text": "quux",
      "created_at": "2017-01-16 00:00:00",
      "updated_at": "2017-01-18 00:00:00"
    }
  ],
  "has_previous": false,
  "previous_cursor": null,
  "has_next": true,
  "next_cursor": {
    "updated_at": "2017-01-18 00:00:00",
    "created_at": "2017-01-14 00:00:00",
    "id": 6
  }
}

Resource Collection

Lampager supports Laravel's API Resources.

Use helper traits on Resource and ResourceCollection.

use Illuminate\Http\Resources\Json\JsonResource;
use Lampager\Laravel\LampagerResourceTrait;

class PostResource extends JsonResource
{
    use LampagerResourceTrait;
}
use Illuminate\Http\Resources\Json\ResourceCollection;
use Lampager\Laravel\LampagerResourceCollectionTrait;

class PostResourceCollection extends ResourceCollection
{
    use LampagerResourceCollectionTrait;
}
$posts = App\Post::lampager()
    ->orderByDesc('id')
    ->paginate();

return new PostResourceCollection($posts);
{
  "data": [/* ... */],
  "has_previous": false,
  "previous_cursor": null,
  "has_next": true,
  "next_cursor": {/* ... */}
}

Classes

Note: See also lampager/lampager.

NameTypeParent ClassDescription
Lampager\Laravel\PaginatorClassLampager\PaginatorFluent factory implementation for Laravel
Lampager\Laravel\ProcessorClassLampager\AbstractProcessorProcessor implementation for Laravel
Lampager\Laravel\PaginationResultClassLampager\PaginationResultPaginationResult implementation for Laravel
Lampager\Laravel\MacroServiceProviderClassIlluminate\Support\ServiceProviderEnable macros chainable from QueryBuilder, ElqouentBuilder and Relation
Lampager\Laravel\LampagerResourceTraitTrait Support for Laravel JsonResource
Lampager\Laravel\LampagerResourceCollectionTraitTrait Support for Laravel ResourceCollection

Paginator, Processor and PaginationResult are macroable.

API

Note: See also lampager/lampager.

Paginator::__construct()
Paginator::create()

Create a new paginator instance.
If you use Laravel macros, however, you don't need to directly instantiate.

static Paginator create(QueryBuilder|EloquentBuilder|Relation $builder): static
Paginator::__construct(QueryBuilder|EloquentBuilder|Relation $builder)
  • QueryBuilder means \Illuminate\Database\Query\Builder
  • EloquentBuilder means \Illuminate\Database\Eloquent\Builder
  • Relation means \Illuminate\Database\Eloquent\Relation

Paginator::transform()

Transform Lampager Query into Illuminate builder.

Paginator::transform(Query $query): QueryBuilder|EloquentBuilder|Relation

Paginator::build()

Perform configure + transform.

Paginator::build(\Lampager\Contracts\Cursor|array $cursor = []): QueryBuilder|EloquentBuilder|Relation

Paginator::paginate()

Perform configure + transform + process.

Paginator::paginate(\Lampager\Contracts\Cursor|array $cursor = []): \Lampager\Laravel\PaginationResult

Arguments

  • (mixed) $cursor
    An associative array that contains $column => $value or an object that implements \Lampager\Contracts\Cursor. It must be all-or-nothing.
    • For initial page, omit this parameter or pass empty array.
    • For subsequent pages, pass all parameters. Partial parameters are not allowd.

Return Value

e.g.

(Default format when using \Illuminate\Database\Eloquent\Builder)

object(Lampager\Laravel\PaginationResult)#1 (5) {
  ["records"]=>
  object(Illuminate\Database\Eloquent\Collection)#2 (1) {
    ["items":protected]=>
    array(5) {
      [0]=>
      object(App\Post)#2 (26) { ... }
      [1]=>
      object(App\Post)#3 (26) { ... }
      [2]=>
      object(App\Post)#4 (26) { ... }
      [3]=>
      object(App\Post)#5 (26) { ... }
      [4]=>
      object(App\Post)#6 (26) { ... }
    }
  }
  ["hasPrevious"]=>
  bool(false)
  ["previousCursor"]=>
  NULL
  ["hasNext"]=>
  bool(true)
  ["nextCursor"]=>
  array(2) {
    ["updated_at"]=>
    string(19) "2017-01-18 00:00:00"
    ["created_at"]=>
    string(19) "2017-01-14 00:00:00"
    ["id"]=>
    int(6)
  }
}

Paginator::useFormatter()
Paginator::restoreFormatter()
Paginator::process()

Invoke Processor methods.

Paginator::useFormatter(Formatter|callable $formatter): $this
Paginator::restoreFormatter(): $this
Paginator::process(\Lampager\Query $query, \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Collection $rows): \Lampager\Laravel\PaginationResult

PaginationResult::toArray()
PaginationResult::jsonSerialize()

Convert the object into array.

IMPORTANT: camelCase properties are converted into snake_case form.

PaginationResult::toArray(): array
PaginationResult::jsonSerialize(): array

PaginationResult::__call()

Call macro or Collection methods.

PaginationResult::__call(string $name, array $args): mixed

e.g.

PaginationResult::macro('foo', function () {
    return ...;
});
$foo = $result->foo();
$first = $result->first();

Author: lampager
Source Code: https://github.com/lampager/lampager-laravel 
License: MIT license

#php #laravel #pagination 

Lampager-laravel: Rapid Pagination for Laravel

Lampager: Rapid Pagination for Various PHP Frameworks

Lampager Core

The core package of Lampager

Requirements

  • PHP: ^5.6 || ^7.0 || ^8.0

Installing

composer require lampager/lampager

Usage

Basically you don't need to directly use this package. For example, if you use Laravel, install lampager/lampager-laravel.

However, you can manually use like this:

use Lampager\Paginator;
use Lampager\ArrayProcessor;

$cursor = [
    'id' => 3,
    'created_at' => '2017-01-10 00:00:00',
    'updated_at' => '2017-01-20 00:00:00',
];

$query = (new Paginator())
    ->forward()
    ->limit(5)
    ->orderByDesc('updated_at') // ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC
    ->orderByDesc('created_at')
    ->orderByDesc('id')
    ->seekable()
    ->configure($cursor);

$rows = run_your_query_using_PDO($query); // Note: SQLite3 driver example is bundled in the tests/StubPaginator.php. Please refer to that.

$result = (new ArrayProcessor())->process($query, $rows);

It will run the optimized query.

(

    SELECT * FROM `posts`
    WHERE `user_id` = 1
    AND (
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` > 3
        OR
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` > '2017-01-10 00:00:00'
        OR
        `updated_at` > '2017-01-20 00:00:00'
    )
    ORDER BY `updated_at` ASC, `created_at` ASC, `id` ASC
    LIMIT 1

) UNION ALL (

    SELECT * FROM `posts`
    WHERE `user_id` = 1
    AND (
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` <= 3
        OR
        `updated_at` = '2017-01-20 00:00:00' AND `created_at` < '2017-01-10 00:00:00'
        OR
        `updated_at` < '2017-01-20 00:00:00'
    )
    ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC
    LIMIT 6

)

And you'll get

object(Lampager\PaginationResult)#1 (5) {
  ["records"]=>
  array(5) {
    [0]=>
    array(5) {
      ["id"]=>
      int(3)
      ["user_id"]=>
      int(1)
      ["text"]=>
      string(3) "foo"
      ["created_at"]=>
      string(19) "2017-01-10 00:00:00"
      ["updated_at"]=>
      string(19) "2017-01-20 00:00:00"
    }
    [1]=>
    array(5) {
      ["id"]=>
      int(5)
      ["user_id"]=>
      int(1)
      ["text"]=>
      string(3) "bar"
      ["created_at"]=>
      string(19) "2017-01-05 00:00:00"
      ["updated_at"]=>
      string(19) "2017-01-20 00:00:00"
    }
    [2]=>
    array(5) {
      ["id"]=>
      int(4)
      ["user_id"]=>
      int(1)
      ["text"]=>
      string(3) "baz"
      ["created_at"]=>
      string(19) "2017-01-05 00:00:00"
      ["updated_at"]=>
      string(19) "2017-01-20 00:00:00"
    }
    [3]=>
    array(5) {
      ["id"]=>
      int(2)
      ["user_id"]=>
      int(1)
      ["text"]=>
      string(3) "qux"
      ["created_at"]=>
      string(19) "2017-01-17 00:00:00"
      ["updated_at"]=>
      string(19) "2017-01-18 00:00:00"
    }
    [4]=>
    array(5) {
      ["id"]=>
      int(1)
      ["user_id"]=>
      int(1)
      ["text"]=>
      string(3) "quux"
      ["created_at"]=>
      string(19) "2017-01-16 00:00:00"
      ["updated_at"]=>
      string(19) "2017-01-18 00:00:00"
    }
  }
  ["hasPrevious"]=>
  bool(false)
  ["previousCursor"]=>
  NULL
  ["hasNext"]=>
  bool(true)
  ["nextCursor"]=>
  array(2) {
    ["updated_at"]=>
    string(19) "2017-01-18 00:00:00"
    ["created_at"]=>
    string(19) "2017-01-14 00:00:00"
    ["id"]=>
    int(6)
  }
}

Question: How about Tuple Comparison?

With this feature, SQL statements should be simpler. However, according to SQL Feature Comparison, some RDBMS, such as SQLServer, do not support this syntax. Therefore, Lampager continuously uses redundant statements.

It is also useful for Doctrine 2 since its DQL lexer does not support the syntax and triggers parse errors.

Classes

NameTypeParent Class
Implemented Interface
Description
Lampager\PaginatorClass Fluent factory for building Query
Lampager\AbstractProcessorAbstract Class Receive fetched records and format them
Lampager\PaginationResultClass Processor wraps result with this by default
Lampager\ArrayProcessorClassLampager\AbstractProcessorSimple Processor implementation for pure PDO
Lampager\ArrayCursorClassLampager\Contracts\CursorSimple Cursor implementation for pure PDO
Arrays are automatically wrapped with this
Lampager\QueryClass SQL configuration container generated by Paginator
Lampager\Query\...Class Child components of Query
Lampager\Contracts\CursorInterface Indicates parameters for retrieving previous/next records
Lampager\Contracts\FormatterInterface Formatter interface pluggable to Processor
Lampager\Concerns\HasProcessorTrait Helper for extended Paginator providing convenient accessibility to Processor

API

Paginator::orderBy()
Paginator::orderByDesc()
Paginator::clearOrderBy()

Add or clear cursor parameter name for ORDER BY statement.
At least one parameter required.

Paginator::orderBy(string $column, string $direction = 'asc'): $this
Paginator::orderByDesc(string $column): $this
Paginator::clearOrderBy(): $this

IMPORTANT: The last key MUST be the primary key.

e.g. $paginator->orderBy('updated_at')->orderBy('id')

Arguments

  • (string) $column
    Table column name.
  • (string) $direction
    "asc" or "desc".

Paginator::limit()

Define the pagination limit.

Paginator::limit(int $limit): $this

Arguments

  • (int) $limit
    Positive integer.

Paginator::forward()
Paginator::backward()

Define the pagination direction.

Paginator::forward(bool $forward = true): $this
Paginator::backward(bool $backward = true): $this

Forward (Default)

    ===============>
[2] [ 3, 4, 5, 6, 7] [8]
 |    |               └ next cursor
 |    └ current cursor
 └ previous cursor
    ===============>
[8] [ 7, 6, 5, 4, 3] [2]
 |    |               └ next cursor
 |    └ current cursor
 └ previous cursor

Backward

    <===============
[2] [ 3, 4, 5, 6, 7] [8]
 |                |   └ next cursor
 |                └ current cursor
 └ previous cursor
    <===============
[8] [ 7, 6, 5, 4, 3] [2]
 |                |   └ next cursor
 |                └ current cursor
 └ previous cursor

IMPORTANT: You need previous cursor to retrieve more results.

Paginator::inclusive()
Paginator::exclusive()

Paginator::inclusive(bool $inclusive = true): $this
Paginator::exclusive(bool $exclusive = true): $this

Change the behavior of handling cursor.

Inclusive (Default)

Current cursor will be included in the current page.

    ===============>
[2] [ 3, 4, 5, 6, 7] [8]
 |    |               └ next cursor
 |    └ current cursor
 └ previous cursor
    <===============
[2] [ 3, 4, 5, 6, 7] [8]
 |                |   └ next cursor
 |                └ current cursor
 └ previous cursor

Exclusive

Current cursor will not be included in the current page.

    ===============>
[2] [ 3, 4, 5, 6, 7] [8]
 |                └ next cursor
 └ current cursor
    <===============
[2] [ 3, 4, 5, 6, 7] [8]
      |               |
      |               └ current cursor
      └ previous cursor

Paginator::unseekable()
Paginator::seekable()

Paginator::unseekable(bool $unseekable = true): $this
Paginator::seekable(bool $seekable = true): $this

Define that the pagination result should contain both of the next cursor and the previous cursor.

  • unseekable() always requires one simple SELECT query. (Default)
  • seekable() may require SELECT ... UNION ALL SELECT ... query when the cursor parameters are not empty.

Unseekable (Default)

    ===============>
[?] [ 3, 4, 5, 6, 7] [8]
      |               └ next cursor
      └ current cursor

Seekable

    ===============>
[2] [ 3, 4, 5, 6, 7] [8]
 |    |               └ next cursor
 |    └ current cursor
 └ previous cursor

Always when the current cursor parameters are empty

===============>
[ 1, 2, 3, 4, 5] [6]
                  └ next cursor

Paginator::fromArray()

Define options from an associative array.

Paginator::fromArray(array $options): $this

Arguments

  • (array) $options
    Associative array that contains the following keys.
    • (int) limit
    • (bool) backward / forward
    • (bool) exclusive / inclusive
    • (bool) seekable / unseekable
    • (string[][]) $orders

e.g.

[
    'limit' => 30,
    'backward' => true,
    'unseekable' => false,
    'orders' => [
        ['created_at', 'asc'],
        ['id', 'asc'],
    ],
]

Paginator::configure()

Generate Query corresponding to the current cursor.

Paginator::configure(Cursor|array $cursor = []): Query

Arguments

  • (mixed) $cursor
    An associative array that contains $column => $value or an object that implements \Lampager\Contracts\Cursor. It must be all-or-nothing.
    • For the initial page, omit this parameter or pass an empty array.
    • For subsequent pages, pass all parameters. Partial parameters are not allowed.

AbstractProcessor::process()

Receive a pair of Query and fetched rows to analyze and format them.

AbstractProcessor::process(Query $query, mixed $rows): mixed

Arguments

  • (Query) $query
  • (mixed) $rows
    Fetched records from database. Typically it should be an array or a Traversable.

Return Value

(mixed)

By default, an instance of \Lampager\PaginationResult is returned. All fields are public.

e.g.

object(Lampager\PaginationResult)#1 (5) {
  ["records"]=>
  array(5) {
    /* ... */
  }
  ["hasPrevious"]=>
  bool(false)
  ["previousCursor"]=>
  NULL
  ["hasNext"]=>
  bool(true)
  ["nextCursor"]=>
  array(2) {
    ["updated_at"]=>
    string(19) "2017-01-18 00:00:00"
    ["created_at"]=>
    string(19) "2017-01-14 00:00:00"
    ["id"]=>
    int(6)
  }
}

Note that

  • hasPrevious/hasNext will be false when there are no more results for the corresponding direction.
  • Either hasPrevious/hasNext will be null when $cursor is empty or seekable() is not be enabled.

PaginationResult::getIterator()

It can be directly traversed using foreach thanks to the interface \IteratorAggregate.

AbstractProcessor::getIterator(): \ArrayIterator

Return Value

(mixed)

ArrayIterator instance that wraps records.

AbstractProcessor::useFormatter()
AbstractProcessor::restoreFormatter()

Override or restore the formatter for the pagination result.

AbstractProcessor::useFormatter(Formatter|callable $formatter): $this
AbstractProcessor::restoreFormatter(): $this

Callable Formatter Example

<?php

use Lampager\Query;
use Lampager\ArrayProcessor;
use Lampager\PaginationResult;

$formatter = function ($rows, array $meta, Query $query) {
    // Drop table prefix in meta properties (e.g. "posts.updated_at" -> "updated_at")
    foreach (array_filter($meta, 'is_array') as $property => $cursor) {
        foreach ($cursor as $column => $field) {
            unset($meta[$property][$column]);
            $segments = explode('.', $column);
            $meta[$property][end($segments)] = $field;
        }
    }
    return new PaginationResult($rows, $meta);
};

$result = (new ArrayProcessor())->useFormatter($formatter)->process($query, $rows);

Class Formatter Example

<?php

use Lampager\Query;
use Lampager\ArrayProcessor;
use Lampager\PaginationResult;
use Lampager\Contracts\Formatter;

class DropTablePrefix implements Formatter
{
    public function format($rows, array $meta, Query $query)
    {
        // Drop table prefix in meta properties (e.g. "posts.updated_at" -> "updated_at")
        foreach (array_filter($meta, 'is_array') as $property => $cursor) {
            foreach ($cursor as $column => $field) {
                unset($meta[$property][$column]);
                $segments = explode('.', $column);
                $meta[$property][end($segments)] = $field;
            }
        }
        return new PaginationResult($rows, $meta);
    }
}

$result = (new ArrayProcessor())->useFormatter(DropTablePrefix::class)->process($query, $rows);

AbstractProcessor::setDefaultFormatter()
AbstractProcessor::restoreDefaultFormatter()

Globally override or restore the formatter.

static AbstractProcessor::setDefaultFormatter(Formatter|callable $formatter): void
static AbstractProcessor::restoreDefaultFormatter(): void

Example (Laravel)

<?php

use Illuminate\Database\Eloquent\Builder;
use Lampager\Query;
use Lampager\Laravel\Processor as IlluminateProcessor;

IlluminateProcessor::setDefaultFormatter(function ($rows, array $meta, Query $query) {

   // Note:
   //    $builder is provided from extended Paginator.
   //    For example, lampager/lampager-laravel provides QueryBuilder, EloquentBuilder or Relation.
   $builder = $query->builder();

   switch ($builder instanceof Builder ? $builder->getModel() : null) {

       case Post::class:
           return (new PostFormatter())->format($rows, $meta, $query);

       case Comment::class:
           return (new CommentFormatter())->format($rows, $meta, $query);

       default:
           return new PaginationResult($rows, $meta);
   }
});

$posts = Post::lampager()->orderBy('created_at')->orderBy('id')->paginate();
$comments = Comment::lampager()->orderBy('created_at')->orderBy('id')->paginate();

Author: lampager
Source Code: https://github.com/lampager/lampager 
License: MIT license

#php #pagination 

Lampager: Rapid Pagination for Various PHP Frameworks

Lampager-cakephp: Rapid Pagination for CakePHP

Lampager for CakePHP

Rapid pagination without using OFFSET

Requirements

Note

Installing

composer require lampager/lampager-cakephp:^2.0

For SQLite users, see SQLite to configure.

Basic Usage

Simply install as a Composer package and use in one or more of the following methods:

  • Use in Controller (via \Lampager\Cake\Datasource\Paginator)
  • Use in Table (via \Lampager\Cake\Model\Behavior\LampagerBehavior)

Use in Controller

At first, load the default Paginator component with the \Lampager\Cake\Datasource\Paginator in your Controller class (AppController is preferable).

namespace App\Controller;

use Cake\Controller\Controller;
use Lampager\Cake\Datasource\Paginator;

class AppController extends Controller
{
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Paginator', [
            'paginator' => new Paginator(),
        ]);
    }
}

Use in a way described in the Cookbook: Pagination. Note the options that are specific to Lampager such as forward, seekable, or cursor.

$query = $this->Posts
    ->where(['Posts.type' => 'public'])
    ->orderDesc('created')
    ->orderDesc('id')
    ->limit(10);

$posts = $this->paginate($query, [
    'forward' => true,
    'seekable' => true,
    'cursor' => [
        'id' => 4,
        'created' => '2020-01-01 10:00:00',
    ],
]);

$this->set('posts', $posts);

Use in Table

Initialize LampagerBehavior in your Table class (AppTable is preferable) and simply use lampager() there.

namespace App\Model\Table;

use Cake\ORM\Table;
use Lampager\Cake\Model\Behavior\LampagerBehavior;

class AppTable extends Table
{
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->addBehavior(LampagerBehavior::class);
    }
}

The query builder (\Lampager\Cake\ORM\Query) extends the plain old \Cake\ORM\Query and is mixed in with \Lampager\Paginator. Note that some of the methods in \Lampager\Paginator, viz., orderBy(), orderByDesc(), and clearOrderBy() are not exposed because their method signatures are not compatible with the CakePHP query builder.

$cursor = [
    'id' => 4,
    'created' => '2020-01-01 10:00:00',
    'modified' => '2020-01-01 12:00:00',
];

/** @var \Lampager\Cake\PaginationResult $latest */
$latest = $this->lampager()
    ->forward()
    ->seekable()
    ->cursor($cursor)
    ->limit(10)
    ->orderDesc('Posts.modified')
    ->orderDesc('Posts.created')
    ->orderDesc('Posts.id');

foreach ($latest as $post) {
    /** @var \Cake\ORM\Entity $post */
    debug($post->id);
    debug($post->created);
    debug($post->modified);
}

The methods from the CakePHP query builder, e.g., where(), are available. \Cake\Database\Expression\QueryExpression is accepted as well.

/** @var \Lampager\Cake\PaginationResult $drafts */
$drafts = $this->lampager()
    ->where(['type' => 'draft'])
    ->forward()
    ->seekable()
    ->cursor($cursor)
    ->limit(10)
    ->orderDesc($this->query()->newExpr('modified'))
    ->orderDesc($this->query()->newExpr('created'))
    ->orderDesc($this->query()->newExpr('id'));

/** @var \Cake\ORM\Entity $sample */
$sample = $drafts->sample();

/** @var int $count */
$count = $drafts->count();

Classes

See also: lampager/lampager.

NameTypeParent Class
Implemented Interface
Description
Lampager\Cake\ORM\QueryClassCake\ORM\QueryFluent factory implementation for CakePHP
Lampager\Cake\Model\Behavior\LampagerBehaviorClassCake\ORM\BehaviorCakePHP behavior which returns Lampager\Cake\ORM\Query
Lampager\Cake\Datasource\PaginatorClassCake\Datasource\PaginatorCakePHP paginatior which delegates to Lampager\Cake\ORM\Query
Lampager\Cake\PaginatorClassLampager\PaginatorPaginator implementation for CakePHP
Lampager\Cake\ArrayProcessorClassLampager\ArrayProcessorProcessor implementation for CakePHP
Lampager\Cake\PaginationResultClassCake\Datasource\ResultSetInterfacePaginationResult implementation for CakePHP
Lampager\Cake\Database\SqliteCompilerClassCake\Database\SqliteCompilerQuery compiler implementation for SQLite
Lampager\Cake\Database\Driver\SqliteClassCake\Database\Driver\SqliteDriver implementation which delegates to Lampager\Cake\Database\SqliteCompiler

Note that \Lampager\Cake\PaginationResult does not extend \Lampager\PaginationResult as it conflicts with \Cake\Datasource\ResultSetInterface.

API

See also: lampager/lampager.

LampagerBehavior::lampager()

Build a Lampager query from Table in exactly the same way as CakePHP.

LampagerBehavior::lampager(): \Lampager\Cake\ORM\Query

Paginator::__construct()
Paginator::create()

Create a new paginator instance. These methods are not intended to be directly used in your code.

static Paginator::create(\Cake\ORM\Query $builder): static
Paginator::__construct(\Cake\ORM\Query $builder)

Paginator::transform()

Transform a Lampager query into a CakePHP query.

Paginator::transform(\Lampager\Query $query): \Cake\ORM\Query

Paginator::build()

Perform configure + transform.

Paginator::build(\Lampager\Contracts\Cursor|array $cursor = []): \Cake\ORM\Query

Paginator::paginate()

Perform configure + transform + process.

Paginator::paginate(\Lampager\Contracts\Cursor|array $cursor = []): \Lampager\Cake\PaginationResult

Arguments

  • (mixed) $cursor
    An associative array that contains $column => $value or an object that implements \Lampager\Contracts\Cursor. It must be all-or-nothing.
    • For the initial page, omit this parameter or pass an empty array.
    • For the subsequent pages, pass all the parameters. The partial one is not allowed.

Return Value

e.g.,

(Default format when using \Cake\ORM\Query)

object(Lampager\Cake\PaginationResult)#1 (6) {
  ["(help)"]=>
  string(44) "This is a Lampager Pagination Result object."
  ["records"]=>
  array(3) {
    [0]=>
    object(Cake\ORM\Entity)#2 (11) { ... }
    [1]=>
    object(Cake\ORM\Entity)#3 (11) { ... }
    [2]=>
    object(Cake\ORM\Entity)#4 (11) { ... }
  ["hasPrevious"]=>
  bool(false)
  ["previousCursor"]=>
  NULL
  ["hasNext"]=>
  bool(true)
  ["nextCursor"]=>
  array(2) {
    ["created"]=>
    object(Cake\I18n\Time)#5 (3) {
      ["date"]=>
      string(26) "2017-01-01 10:00:00.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    ["id"]=>
    int(1)
  }
}

PaginationResult::__call()

\Lampager\Cake\PaginationResult implements \Cake\Datasource\ResultSetInterface. For how to make the best use of the PaginationResult, please refer to the Cookbook: Working with Result Sets.

Examples

This section describes the practical usage of lampager-cakephp.

Use in Controller

The example below shows how to accept a cursor parameter from a request and pass it through PaginatorComponent::paginate(). Be sure that your AppController has properly initialized Paginator as above.

namespace App\Controller;

class PostsController extends AppController
{
    /**
     * This method shows how to pass options by a query and array.
     */
    public function query(): void
    {
        // Get cursor parameters
        $previous = json_decode($this->request->getQuery('previous_cursor'), true);
        $next = json_decode($this->request->getQuery('next_cursor'), true);
        $cursor = $previous ?: $next ?: [];

        // Query expression can be passed to PaginatorComponent::paginate() as normal
        $query = $this->Posts
            ->where(['Posts.type' => 'public'])
            ->orderDesc('created')
            ->orderDesc('id')
            ->limit(15);

        /** @var \Lampager\Cake\PaginationResult<\Cake\ORM\Entity> $posts */
        $posts = $this->paginate($query, [
            // If the previous_cursor is not set, paginate forward; otherwise backward
            'forward' => !$previous,
            'cursor' => $cursor,
            'seekable' => true,
        ]);

        $this->set('posts', $posts);
    }

    /**
     * This method shows how to pass options from an array.
     */
    public function options(): void
    {
        // Get cursor parameters
        $previous = json_decode($this->request->getQuery('previous_cursor'), true);
        $next = json_decode($this->request->getQuery('next_cursor'), true);
        $cursor = $previous ?: $next ?: [];

        /** @var \Lampager\Cake\PaginationResult<\Cake\ORM\Entity> $posts */
        $posts = $this->paginate('Posts', [
            // Lampager options
            // If the previous_cursor is not set, paginate forward; otherwise backward
            'forward' => !$previous,
            'cursor' => $cursor,
            'seekable' => true,

            // PaginatorComponent config
            'conditions' => [
                'type' => 'public',
            ],
            'order' => [
                'created' => 'DESC',
                'id' => 'DESC',
            ],
            'limit' => 15,
        ]);

        $this->set('posts', $posts);
    }
}

And the pagination links can be output as follows:

// If there is a next page, print pagination link
if ($posts->hasPrevious) {
    echo $this->Html->link('<< Previous', [
        'controller' => 'posts',
        'action' => 'index',
        '?' => [
            'previous_cursor' => json_encode($posts->previousCursor),
        ],
    ]);
}

// If there is a next page, print pagination link
if ($posts->hasNext) {
    echo $this->Html->link('Next >>', [
        'controller' => 'posts',
        'action' => 'index',
        '?' => [
            'next_cursor' => json_encode($posts->nextCursor),
        ],
    ]);
}

Supported database engines

MySQL, MariaDB, and PostgreSQL

Supported!

Microsoft SQL Server

Not supported.

SQLite

Supported but requires an additional configuration.

In SQLite UNION ALL statements cannot combine SELECT statements that have ORDER BY clause. In order to get this to work, those SELECT statements have to be wrapped by a subquery like SELECT * FROM (...). CakePHP not natively handling this situation, Lampager for CakePHP introduces \Lampager\Cake\Database\Driver\Sqlite that needs to be installed on your application. Configure like the following in your config/app.php:

return [
    'Datasources' => [
        'default' => [
            'className' => Connection::class,
            'driver' => \Lampager\Cake\Database\Driver\Sqlite::class,
            'username' => '********',
            'password' => '********',
            'database' => '********',
        ],
    ],
];

Author: lampager
Source Code: https://github.com/lampager/lampager-cakephp 
License: MIT license

#php #cakephp #pagination 

Lampager-cakephp: Rapid Pagination for CakePHP

Effortless Pagination With Laravel!

We all know what pagination is. It basically means separating our contents both in print or in digital form into discreet pages. But like Laravel we at UI-Lib try our best to make you understand anything we write, effortlessly. So for the newbies, by saying pagination we mean the thing is shown in the image below,

pagination laravel

Now let us understand how we can easily paginate our data. For this, we need to perform the following steps:

  1. Creating Project with Laravel
  2. Database Configuration
  3. Defining and Migrating Model
  4. Fake Data Generation
  5. Controller and Route Creation
  6. Create View File
  7. Use Pagination
  8. Run Laravel Application

Let’s dive into the process where we can see how easily we can use pagination in our project with Laravel.

Read more 👇🤩

 

#laravel #pagination #laravel9 #backend 

Effortless Pagination With Laravel!
Jaden  Haag

Jaden Haag

1637632800

Generating Pagination using Web API [For Beginners]

Use Pagination in Web API Design. Paging in a REST API. ASP.NET Core Web API.

Here I am going to show how we can use pagination in Wep Api step by step Asp.Net Core

1. Create PagedList generic class.
2. Make constructor of PagedList Class.
3. Create a pagedList create method.
4. Create PaginationResourceParameters.
5. PaginationResourceParameters Done.
6. Create ResourceUriType Enum.
7. I already have an object class named Student.cs. 
   I want to apply my pagination on StudentWebApiController.
8. Create StudentResourceParameters class.
9. Create IStudentRepository.
10. Create StudentRepository.cs class.
11. Register Student repository in startup.cs.
12. Add IUrlHelper and IActionContextAccessor in startup.cs.
13. Basic part is done. Create StudentsController and test our project.
14. Lets create CreateResourceUri method.
15. CreateResourceUri method done. Now I am going apply this method.
16. Create StudentViewModel.cs (DTO related)
17. Ok done.
18. Now I am going to test this project. open postman for testing our GetStudents API.
19. I have already 50+ student entry in my database.

Output :
20. So by default 10 students are showing as output.
21. pageSize = 5 means I want to show maximum 5 students in each page.
22. pageNumber = 1 means display page 1
23. Now I want to see next page's 5 students.
24. Now I want to see 10 students in my first page.
25. Want to see 10 students of my 3rd page.
26. So from header part you can get idea from x-pagination ==
 totalCount:59,
 pageSize:10,
 currentPage:3,
 totalPage:6,
 previousPageLink:null,
 nextPageLink:null

#web-development  #pagination  #api 

Generating Pagination using Web API [For Beginners]

A Flutter Package Which Makes Pagination Easier

Paginable is a Flutter package which makes pagination easier.

An animated image of the PaginableListViewBuilder An animated image of the PaginableListViewBuilder with error An animated image of the PaginableListViewBuilder An animated image of the PaginableListViewBuilder with error

Paginable Widgets

Usage

Using PaginableListViewBuilder

As I mentioned earlier, this widget is just a paginable's version of ListView.builder which provides only three more parameters.

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('PaginableListViewBuilder'),
        ),
        body: PaginableListViewBuilder(
            loadMore: () async {},
            errorIndicatorWidget: (exception, tryAgain) => Container(
                  color: Colors.redAccent,
                  height: 130,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        exception.toString(),
                        style: const TextStyle(fontSize: 16),
                      ),
                      const SizedBox(
                        height: 16.0,
                      ),
                      ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor:
                              MaterialStateProperty.all(Colors.green),
                        ),
                        onPressed: tryAgain,
                        child: const Text('Try Again'),
                      ),
                    ],
                  ),
                ),
            progressIndicatorWidget: const SizedBox(
              height: 100,
              child: Center(
                child: CircularProgressIndicator(),
              ),
            ),
            itemBuilder: (context, index) => ListTile(
                  leading: CircleAvatar(
                    child: Text(
                      index.toString(),
                    ),
                  ),
                ),
            itemCount: 20),
      ),
    );
  }
}
  • loadMore takes an async function which will be executed when the scroll is almost at the end.
  • errorIndicatorWidget also takes a function which contains two parameters, one being of type Exception and other being a Function(), returning a widget which will be displayed at the bottom of the scrollview when an exception occurs in the async function which we passed to the loadMore parameter. The parameter with type Exception will contain the exception which occured while executing the function passed to the parameter loadMore if exception occured and the parameter with type Function() will contain the same function which we passed to the loadMore parameter.
  • progressIndicatorWidget takes a widget which will be displayed at the bottom of the scrollview to indicate the user that the async function we passed to the loadMore parameter is being executed.

Using PaginableCustomScrollView with PaginableSliverChildBuilderDelegate

Paginable also supports SliverList, but instead of using CustomScrollView one need to use PaginableCustomScrollView along with PaginableSliverChildBuilderDelegate as the delegate.

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: PaginableCustomScrollView(
          loadMore: () async {},
          slivers: [
            const SliverAppBar(
              title: Text('PaginableSliverChildBuilderDelegate'),
              floating: true,
            ),
            SliverList(
              delegate: PaginableSliverChildBuilderDelegate(
                (context, index) => ListTile(
                  leading: CircleAvatar(
                    child: Text(
                      index.toString(),
                    ),
                  ),
                ),
                childCount: 20,
                errorIndicatorWidget: (exception, tryAgain) => Container(
                  color: Colors.redAccent,
                  height: 130,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        exception.toString(),
                        style: const TextStyle(fontSize: 16),
                      ),
                      const SizedBox(
                        height: 16.0,
                      ),
                      ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor:
                              MaterialStateProperty.all(Colors.green),
                        ),
                        onPressed: tryAgain,
                        child: const Text('Try Again'),
                      ),
                    ],
                  ),
                ),
                progressIndicatorWidget: const SizedBox(
                  height: 100,
                  child: Center(
                    child: CircularProgressIndicator(),
                  ),
                ),
              ).build(),
            ),
          ],
        ),
      ),
    );
  }
}

You might miss that the parameter loadMore is in PaginableCustomScrollView, the rest two errorIndicatorWidget & progressIndicatorWidget are in PaginableSliverChildBuilderDelegate and we are calling build() on PaginableSliverChildBuilderDelegate. It is mandatory to call build() on PaginableSliverChildBuilderDelegate because it returns a SliverChildBuilderDelegate.

It is worth noting that Paginable is only resposible for executing the async function passed to the loadMore parameter when the scroll is almost at the end, displaying errorIndicatorWidget at the bottom of the scrollview when an exception occurs in the loadMore and displaying progressIndicatorWidget at the bottom of the scrollview to indicate the user that the loadMore is being executed.

Paginable is not resposible for managing states, to know about different ways of managing states in Flutter, https://flutter.dev/docs/development/data-and-backend/state-mgmt/options

Contribution

If you would like to contribute to the package (e.g. by improving the documentation, solving a bug or adding a cool new feature), you are welcomed and very much appreciated to send us your pull request and if you find something wrong with the package, please feel free to open an issue.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add paginable

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  paginable: ^1.1.0

Alternatively, your editor might support or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:paginable/paginable.dart'; 

example/README.md

Examples

Using PaginableListViewBuilder

This example helps to understand the usage of PaginableListViewBuilder.

Jump To Source

Using PaginableCustomScrollView with PaginableSliverChildBuilderDelegate

This example helps to understand how we can use PaginableCustomScrollView with PaginableSliverChildBuilderDelegate to perform pagination.

Jump To Source 

Download Details:

Author: chinkysight

Source Code: https://github.com/chinkysight/paginable

#flutter  #dart #pagination 

A Flutter Package Which Makes Pagination Easier
Jaden  Haag

Jaden Haag

1625784240

Blazor : Paging and Filtering using MongoDB

Blazor : Paging and Filtering using MongoDB || Pagination || Filter || NoSQL || .NET 5.0

Complete CRUD BULK Operation of MongoDB with Blazor proper example.
Source Code : https://payhip.com/b/VeIFq


👍Best MongoDB Courses :

  1. https://rb.gy/7wyeae
  2. https://rb.gy/fp3l1g
  3. https://rb.gy/5lkj8y

👍Top 5 Blazor Courses :

✔️For Beginners :

  1. https://rb.gy/gofyb9
  2. https://rb.gy/uczpzr
  3. https://rb.gy/yn3kuk
  4. https://rb.gy/mkm5rt
  5. https://rb.gy/ue7vfq

✔️Most Popular :

  1. https://rb.gy/bbafjr
  2. https://rb.gy/vu5ypu
  3. https://rb.gy/ruqutg
  4. https://rb.gy/ukctvk
  5. https://rb.gy/vdph2t

Download .NET 5.0 : https://dotnet.microsoft.com/download/dotnet/5.0

👍MongoDB Series with Blazor :

  1. Install And Test Basic Query Statements : https://youtu.be/PfEhYn3YD9Q
  2. CRUD Operation Queries : https://youtu.be/ow394OJa3g0
  3. An Introduction to MongoDB Compass : https://youtu.be/sMGRFk7_hzA
  4. CRUD Blazor with MongoDB : https://youtu.be/xyIEAqrgXiM
  5. Save and Display Photo MongoDB : https://youtu.be/oRWE9TfVrWs
  6. Bulk CRUD MongoDB : https://youtu.be/cNf-1jKnLhU
  7. Paging and Filtering using MongoDB : https://youtu.be/A2GY2RLRPS0
  8. PDF Upload and Download : https://youtu.be/GaTrjxrkpVM

👍MongoDB Series with ASP.NET Core :

  1. Install And Test Basic Query Statements : https://youtu.be/PfEhYn3YD9Q
  2. CRUD Operation Queries : https://youtu.be/ow394OJa3g0
  3. An Introduction to MongoDB Compass : https://youtu.be/sMGRFk7_hzA
  4. ASP.NET Core CRUD using MongoDB : https://youtu.be/dsvL22_w88I
  5. Save and Display Image/Photo : https://youtu.be/zAQYzL4qOss
  6. BULK CRUD : https://youtu.be/iayO1j3tGKA
  7. PDF Upload and Download : https://youtu.be/RtnIioN3suE
  8. CRUD 2 : https://youtu.be/chhjsvl5cVc

👍 For Inspiration Your Contribution :

✔️ Buy Me A Coffee : https://www.buymeacoffee.com/ThumbIKR

👍 FOLLOW US :

✔️Follow Website : https://thumbikr.blogspot.com
✔️Facebook Page : https://www.facebook.com/thumbIKR.official/

👍Channel Other Features :

✔️ All Playlists : https://thumbikr.blogspot.com/2021/06/playlist-of-our-channel-thumb-ikr.html
✔️ Source Codes : https://payhip.com/ThumbIKR/collection/all

👍 RELATED TAGS :

pagination mongodb c# driver, mongodb pagination query c#, mongodb c# paging, c# mongodb pagination, mongodb paging c#, c# mongodb paging, pagination mongodb c#, mongodb c# driver pagination, mongodb c# paging, mongodb c# filter in list,mongodb c# filter definition builder, mongodb c# filter multiple fields, mongodb c# filter eq case insensitive, mongodb c# filter contains, mongodb c# filter not equal, mongodb c# filter array contains, mongodb filter all c#, mongodb c# filter and, mongodb c# aggregate filter, c# mongodb filter any, mongodb filter array elements c#, mongodb c# filter builder, mongodb c# filter by id, mongodb c# filter by date, mongodb c# filter bsondocument, c# mongodb filter builder not equal, mongodb c# build filter, c# mongodb filter by type, c# mongodb filter by list, mongodb filter contains c#, mongodb c# filter case insensitive, mongodb filter collection c#, mongodb filter multiple conditions c#, mongodb c# create filter, Mongodb c# driver filter definition, mongodb c# driver filter regex, mongodb filter eq c#, mongodb c# filter example, mongodb filter expression c#, mongodb c# filter exists, mongodb c# find filter, mongodb c# findasync filter, mongodb c# find without filter, c# mongodb filter guid, mongodb builders filter c#, mongodb filter in c#

#pagination #filtering #mongodb #blazor

Blazor : Paging and Filtering using MongoDB
Dexter  Goodwin

Dexter Goodwin

1624443060

Pagination with the Rick and Morty API

Most websites today can’t show all the data users need to see at once. It is better not to show all data because it makes for a better user experience and it also makes websites easier to navigate.

In order to show more data, a feature known as pagination can be implemented. I decided to give pagination a try and implement it using the Rick and Morty API. I chose this API because it made it easier to implement pagination in my opinion. I will show my own approach to pagination.

I am going to start my project with the minimum. I will only be keeping the App and index CSS files and the App and index components. I will be using hooks so I will be importing the useState and useEffect hooks from React. First I will declare all the state variables that I need in the App component.

const [loading, setLoading] = useState(true)

const [characters, setCharacters] = useState([])
const [currentPageUrl, setCurrentPageUrl] = useState("https://rickandmortyapi.com/api/character")
const [nextPageUrl, setNextPageUrl] = useState()
const [prevPageUrl, setPrevPageUrl] = useState()
const [pages, setPages] = useState()

#javascript #pagination #react #programming

Pagination with the Rick and Morty API

Pagination Example In Laravel

In this post I will show you pagination example in laravel, as we all know pagination is very common feature in all websites, if we want to display specific number of details or images then we can use pagination.

aravel provide paginate method and it will automatically takes care of setting the proper limit and offset based on the current page being viewed by the user.here i will show you how to use pagination in laravel, So I have try paginate method in laravel.

Pagination Example In Laravel

https://websolutionstuff.com/post/pagination-example-in-laravel

#pagination example in laravel #laravel #pagination #paginate method #how to use pagination in laravel #pagination in laravel

Pagination Example In Laravel

John Smith

1618991273

How to Add Custom Select Box pagination in jQuery DataTable with Ajax PHP

In this post you can find the solution of How can we add Dropdown or Select Box pagination in jQuery DataTable without using DataTable Pagination plugin with default pagination. How to implement multiple pagnation in single DataTable with Ajax jQuery and PHP. How to Set two different type of pagination on DataTable. How can we go directly specific page in jQuery DataTable with Ajax PHP. For this all query you can get answer from this post. In this post you can find complete source code and even you can also check online demo also.

https://www.webslesson.info/2021/04/how-to-add-custom-select-box-pagination-in-jquery-datatable-with-ajax-php.html

#php #ajax #jquery #datatable #pagination

How to Add Custom Select Box pagination in jQuery DataTable with Ajax PHP

I Dev

1617605661

React Table Pagination (Server side) with Search | react-table v7 - BezKoder

In this tutorial, I will show you how to make React Table Pagination (Server side) with Search in a React Hooks Application using react-table v7 for data table and Material-UI for pagination.

Full Article: https://bezkoder.com/react-table-pagination-server-side/

React Table Pagination (Server side) with Search example

One of the most important things to make a website friendly is the response time, and pagination comes for this reason. For example, this bezkoder.com website has hundreds of tutorials, and we don’t want to see all of them at once. Paging means displaying a small number of all, by a page.

Assume that we have tutorials table in database like this:

Alt Text

Our React.js app will display the result with react-table pagination (server side):

Alt Text

Change to a page with larger index:

Alt Text

We can change quantity of items per page (page size):

Alt Text

Or table pagination with search:

Alt Text

The API for this React client can be found at one of following posts:

These Servers will exports API for pagination (with/without filter), here are some url samples:

  • /api/tutorials?page=1&size=5
  • /api/tutorials?size=5: using default value for page
  • /api/tutorials?page=1: using default value for size
  • /api/tutorials?title=data&page=1&size=3: pagination & filter by title containing 'data'

This is structure of the response for the HTTP GET request:

{
    "totalItems": 8,
    "tutorials": [...],
    "totalPages": 3,
    "currentPage": 1
}

We actually only need to use tutorials and totalPages when working with Material-UI.

For step by step and Github source code, please visit:
https://bezkoder.com/react-table-pagination-server-side/

Further Reading

Related Posts:

Serverless with Firebase:

#react #javascript #web-development #programming #pagination #node

React Table Pagination (Server side) with Search | react-table v7 - BezKoder

csshint .com

1614791988

15 Bootstrap Pagination - csshint - A designer hub

Latest Collection of free Bootstrap Pagination code examples:

#bootstrap #pagination

15 Bootstrap Pagination - csshint - A designer hub