jlrdw's avatar
Level 75

Lengthaware paginator

When ver 5 came out @bestmomo helped me with Lengthaware code, I recently tested it on ver 5.4 it still works good:

  • make a Services folder under app
  • create a file called LengthPager.php

Code for LengthPager.php

namespace App\Services;

use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;

abstract class LengthPager
{

    /**
     * Create paginator
     *
     * @param  Illuminate\Support\Collection  $collection
     * @param  int     $total
     * @param  int     $perPage
     * @return string
     */
    public static function makeLengthAware($collection, $total, $perPage, $appends = null)
    {
        $paginator = new LengthAwarePaginator(
                $collection, $total, $perPage, Paginator::resolveCurrentPage(), ['path' => Paginator::resolveCurrentPath()]
        );

        if ($appends)
            $paginator->appends($appends);

        return str_replace('/?', '?', $paginator->render());
        
    }

}

Then at the top of controller

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use App\Services\LengthPager;
use Illuminate\Http\Request;

A quick test method

public function indexTest(Request $request)
    {
        If (!empty($request->input('page'))) {
            $page = $request->input('page');
        } else {
            $page = "1";
        }
        $perpage = "5";
        $offset = ($page - 1) * $perpage;
        $krows = DB::select('select COUNT(dogid) as count from dc_dogs where adopted = :adpt', ['adpt' => 0]);
        $numrows = $krows[0]->count;
        echo $numrows;
        $t1 = "b";   // just test for querystring
        $dogs = DB::table('dc_dogs')
                        ->where('adopted', '=', 0)
                        ->skip($offset)->take($perpage)->get();
        $pagelinks = LengthPager::makeLengthAware($dogs, $numrows, $perpage, ['t1' => $t1]);
        return view('dog/index')
                ->with('dogs', $dogs)
                ->with('pagelinks', $pagelinks);
    }

And view

<?php

foreach ($dogs as $dog) {
    echo $dog->dogname . "<br>";
}
echo $pagelinks;

At this point the page links will show vertical, they need style. Something like:

<style>
     td, th {
      border: solid 1px black;
    }    
    .pagination li {display:inline; margin-right:5px; padding:3px;}

        .pagination a {
            border: 1px solid #D5D5D5;
            color: #666666;
            font-size: 11px;
            font-weight: bold;
            height: 25px;
            padding: 4px 8px;
            text-decoration: none;
            margin:2px;
        }
        .pagination a:hover, .pagination a:active {
            background:#efefef;
        }
        .pagination span.current {
            background-color: #687282;
            border: 1px solid #D5D5D5;
            color: #ffffff;
            font-size: 11px;
            font-weight: bold;
            height: 25px;
            padding: 4px 8px;
            text-decoration: none;
           margin:2px;
        }
        .pagination span.disabled {
            border: 1px solid #EEEEEE;
            color: #DDDDDD;
            margin: 2px;
            padding: 2px 5px;
        }



    </style>

This works perfect in version 5.4 And of course the default app.css works fine.

0 likes
10 replies
jlrdw's avatar
Level 75

I tested above after installing laravel 5.5, still works good. And boo boo in css, .pagination span.current should be .active and .pagination span.disabled can be just .disabled Sorry I posted the wrong css. But all works.

jlrdw's avatar
Level 75

An update to length aware: You might not want to use a separate class. If using once only right in controller method I have found the best usage is:

  • As above get the count, perpage, offset
  • Name the query something like $quy
    $pagingQuery = "LIMIT {$offset}, {$perpage}";
        $sql = "SELECT * FROM dc_dogs " . $pagingQuery;
        $sth =  DB::getPdo()->prepare($sql);
        $sth->execute();
        $quy = $sth->fetchAll(\PDO::FETCH_OBJ);
// here getPdo() used but querybuilder or eloquent is fine

Name the variable for paginator the useful name:

$dogs = LengthAwarePaginator($quy, $numrows, $perpage);

Notice here I only pass $quy, count, per page.

I handle parameters with the current page like this:

$params = ['t1' => $t1, 'page' => $page];

So prior to calling view you'd have:

$dogs = LengthAwarePaginator($quy, $numrows, $perpage);
$params = ['t1' => $t1, 'page' => $page];
// call for view here, similar to above
// but ->with('params', $params);

Now the view

PHP example given just convert to blade as needed.

<?php echo str_replace('/?', '?', $dogs->appends($params)->render('custompager')); ?>
/////  if using a custom pager

or

<?php echo str_replace('/?', '?', $dogs->appends($params)->render()); ?>
/////  No custom pager

For more on the custom pager template I put an example here: https://laracasts.com/discuss/channels/guides/getpdo-usage

The str_replace is necessary.

jlrdw's avatar
Level 75

Still works in laravel 5.7.

And I am putting custom template example here also. This template just gives less links and works good on smaller mobile screens if the links are still desired:

Image of links:

https://imgur.com/zwNEDlz

Notice it does not show second page and next to last page.

Template custompager.blade.php:

@if ($paginator->hasPages())
    <ul class="pagination pagination">
        {{-- Previous Page Link --}}
        @if ($paginator->onFirstPage())
            <li class="disabled"><span>«</span></li>
        @else
            <li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>
        @endif

        @if($paginator->currentPage() > 3)
            <li class="hidden-xs"><a href="{{ $paginator->url(1) }}">1</a></li>
        @endif
        @if($paginator->currentPage() > 4)
            <li><span>...</span></li>
        @endif
        @foreach(range(1, $paginator->lastPage()) as $i)
            @if($i >= $paginator->currentPage() - 2 && $i <= $paginator->currentPage() + 2)
                @if ($i == $paginator->currentPage())
                    <li class="active"><span>{{ $i }}</span></li>
                @else
                    <li><a href="{{ $paginator->url($i) }}">{{ $i }}</a></li>
                @endif
            @endif
        @endforeach
        @if($paginator->currentPage() < $paginator->lastPage() - 3)
            <li><span>...</span></li>
        @endif
        @if($paginator->currentPage() < $paginator->lastPage() - 2)
            <li class="hidden-xs"><a href="{{ $paginator->url($paginator->lastPage()) }}">{{ $paginator->lastPage() }}</a></li>
        @endif

        {{-- Next Page Link --}}
        @if ($paginator->hasMorePages())
            <li><a href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>
        @else
            <li class="disabled"><span>»</span></li>
        @endif
    </ul>
@endif
jlrdw's avatar
Level 75

Suggested I show an example of bindings:

    public function veryTemp()
    {

        $page = Request::input('page', '1');
        $bdate = Request::input('begindate');
        $edate = Request::input('enddate');

        $perpage = "10";
        $offset = ($page - 1) * $perpage;

        if (!$page === '1') {
            $numrows = Session::get('numrows');
        } else {
            $krows = "SELECT COUNT(*) AS kount FROM ";
            $krows .= "(select distinct `account_types`.`AccountType` AS `AccountType`,`accounts`.`AccountNumber` ";
            $krows .= "AS `AccountNumber`,`accounts`.`AccountName` AS `AccountName`,sum(`transactions`.`Expense`) ";
            $krows .= "AS `Sum_Expense`,sum(`transactions`.`Income`) AS `Sum_Income` ";
            $krows .= "from ((`account_types` join `accounts` on((`account_types`.`AccountTypeID` = ";
            $krows .= "`accounts`.`AccountTypeID`))) join `transactions` on((`accounts`.`AccountID` = ";
            $krows .= "`transactions`.`AccountID`))) ";
            $krows .= "where (`transactions`.`TransactionDate` Between '$bdate' and  '$edate') ";
            $krows .= "group by `account_types`.`AccountType`,`accounts`.`AccountNumber`,`accounts`.`AccountName`) ";
            $krows .= "as t1";

            $sthk = DB::connection('mysqlcb')->getPdo()->prepare($krows);
            $sthk->execute();
            $quyk = $sthk->fetch(\PDO::FETCH_OBJ);
            $numrows = $quyk->kount;
            Session::put('numrows', $numrows);
        }

        $pagingQuery = "LIMIT {$offset}, {$perpage}";

        $sql = "select distinct `account_types`.`AccountType` AS `AccountType`,`accounts`.`AccountNumber` ";
        $sql .= "AS `AccountNumber`,`accounts`.`AccountName` AS `AccountName`,sum(`transactions`.`Expense`) ";
        $sql .= "AS `Sum_Expense`,sum(`transactions`.`Income`) AS `Sum_Income` ";
        $sql .= "from ((`account_types` join `accounts` on((`account_types`.`AccountTypeID` = ";
        $sql .= "`accounts`.`AccountTypeID`))) join `transactions` on((`accounts`.`AccountID` = ";
        $sql .= "`transactions`.`AccountID`))) ";
        $sql .= "where (`transactions`.`TransactionDate` Between :bdate and  :edate) ";
        $sql .= "group by `account_types`.`AccountType`,`accounts`.`AccountNumber`,`accounts`.`AccountName` ";
        $sql .= $pagingQuery;

        $sth = DB::connection('mysqlcb')->getPdo()->prepare($sql);
        $params = ['bdate' => $bdate, 'edate' => $edate];
        $sth->execute($params);
        $quy = $sth->fetchAll(\PDO::FETCH_OBJ);
        $report = new LengthAwarePaginator($quy, $numrows, $perpage);
        $pagelinks = ['bdate' => $bdate, 'edate' => $edate, 'page' => $page];

        $title = 'Monthly Report';
        return view('account.report', compact('report', 'pagelinks', 'title'));
    }

View:

@php echo str_replace('/?', '?', $report->appends($pagelinks)->render())  @endphp

// Or if a custom pager

@php echo str_replace('/?', '?', $report->appends($pagelinks)->render('custompager'))  @endphp

And notice you don't need to count every time if you use session.

Also of course query builder, ORM can be used, but you need a total count.

And properly bind parameters.

And example of binding using db facade is:

https://laracasts.com/discuss/channels/laravel/sql-native-to-query-builder

Also, I just use jquery date picker for dates.

mariam528's avatar

i have a question here.. I was trying to use your pagination method ..it is working fine..but when i apply any filter..it just overrides my filter value in URL whenever i try to go to any other page. an example for your understanding if my current URL is something like this

https://website.com?name=anna&class=2

but when i go to second page.it override my parameters like this

https://website.com?page=2

could you perhaps help me with this..as i need exactly this type of pagination

MickyFang's avatar

@mariam528 in your template:

@if(request()->all())
    {!! $lists->appends(request()->all())->links() !!}
@else
    {!! $lists->links() !!}
@endif
CamKem's avatar

2023 Updated method to create a custom mapping for paginate() function.

  1. create a class that extends LengthAwarePaginator, example:
<?php

namespace App\Pagination;

use Illuminate\Pagination\LengthAwarePaginator;

class CustomPaginator extends LengthAwarePaginator
{

    public function toArray(): array
    {
        return [
            'data' => $this->items->toArray(),
            'meta' => [
                'current_page' => $this->currentPage(),
                'first_page_url' => $this->url(1),
                'from' => $this->firstItem(),
                'last_page' => $this->lastPage(),
                'last_page_url' => $this->url($this->lastPage()),
                'next_page_url' => $this->nextPageUrl(),
                'path' => $this->path(),
                'per_page' => $this->perPage(),
                'prev_page_url' => $this->previousPageUrl(),
                'to' => $this->lastItem(),
                'total' => $this->total(),
            ],
            'links' => $this->linkCollection(),
        ];
    }

}
  1. Swap out the paginator instances on in the AppServiceProvider class boot() method.
        $this->app->bind(
            \Illuminate\Pagination\LengthAwarePaginator::class,
            \App\Pagination\CustomPaginator::class
        );
1 like
lesstenperfect's avatar

@CamKem I have been looking for 'links' => $this->linkCollection(), all over. I could not find any mention of meta->links in side the ResourceCollection. Not sure why documentation does not show meta -> links as part fo the setup.

1 like

Please or to participate in this conversation.