menu

Questions & Answers

Laravel Multiple Pagination in one page

I'm having some trouble with my pagination. I'm having two tables with data from a database and I paginated it with laravel Paginator.

Now my problem is when you go to, for example, page 2 it adds ?page=2 but that makes the first table go to page 2 too.

Is there anyway to get something like this ?

page_table1={number}&page_table2={number}

so you don't apply the page change on other tables.

Answers(11) :

Sounds to me like you need to update the paging tool so that it has an extra param that identifies the table as it's not smart enough to know that it might have 2 instances of itself on the same page.. DOH!

Rather than having to set the current page for all tables through a URL, ideally you want to store the active page number in the session by table id THEN, your script only has to worry about instructions and not worry about ensuring it carries existing page numbers also.

Here's a really draft concept....

// The page would be $tableId + "," + $pageNum
// Passed as a value of "page"
$goTo = "$tableId,$pageNum";
?page=$goTo

Then, when it gets to the part that's getting your table data it would do something like

if(!empty($_GET["page"])){
    list($tableId,$pageNum) = explode(",",$_GET["page"]);

    // Store table's active page number
    $_SESSION["_tableBrowser"][$tableId] = $pageNum;


    // Your table reader would then look at the session for the active page number
    // and not what is in $_GET
}

Unfortunately I can't test this code right now, but browsing at the docs and the code (in Illuminate/Pagination/Environment) I guess you could something like this:

# use default 'page' for this
$collection1 = Model::paginate(20);

# use custom 'other_page' for this
$collection2 = Model2::paginate(20);
$collection2->setPageName('other_page');

The setPageName() method isn't documented in the docs, but it's a public method alongside those indicated in the documentation, so it should be working fine. FOr reference, this is the declaration (l. 171-180 in vendor/laravel/framework/src/Illuminate/Pagination/Environment.php):

/**
 * Set the input page parameter name used by the paginator.
 *
 * @param  string  $pageName
 * @return void
 */
public function setPageName($pageName)
{
    $this->pageName = $pageName;
}

Now take into consideration that you will have another query string appended to the url, so you need to tell the pagination to consider it. Use the appends() method:

$collection1->appends(array_except(Request::query(), 'page'))->links();

$collection2->appends(array_except(Request::query(), 'other_page'))->links();

That is, tell each Presenter to build up the url with all the query strings (the array resulting from Request::query() without the current index used by the paginator, or you'll end up with a double value). array_except() is a Laravel built in array helper that returns the given array (1st parameter) purged of the passed index (2nd parameter).

I'll try to test this code as soon as I can, but it should work. Let me know in any case!

Comments:
2023-01-17 00:47:54
I was not able to get anything working using the method you described. Where you?
2023-01-17 00:47:54
$collection2->setPageName('other_page'); >>> call_user_func_array() expects parameter 1 to be a valid callback, class 'Illuminate\Support\Collection' does not have a method 'setPageName'
2023-01-17 00:47:54
To the downvoters, consider that this answer might refer to a different Laravel version from the one you're using now and what worked then might not work in a newer version. Just saying.
2023-01-17 00:47:54
@DamienPirsy Do you know if this still works in laravel 5.6 ? I know it has been a while
2023-01-17 00:47:54
I don't think this answer ever worked, I've checked back in versions and you must provide the page name on ::paginate for it to take effect, always have
2023-01-17 00:47:54
i think its currently in AbstractPaginator.php also there is a getPageName()
2023-01-17 00:47:54
and yes its still working setPageName()/getPageName() laravel 5.8

This is an easy solution I've found on Laravel 4.

Controller

Change the page name before you make the paginator:

Paginator::setPageName('page_a');
$collection_A = ModelA::paginate(10);

Paginator::setPageName('page_b');
$collection_B = ModelB::paginate(10);

View

Do the same: change the page name before you print the links

Paginator::setPageName('page_a');
$collection_A->links();

Paginator::setPageName('page_b');
$collection_B->links();

If you don't want to lose any page state while you navigate to another page, append to the links the current page from all collections:

Paginator::setPageName('page_a');
$collection_A->appends('page_b', Input::get('page_b',1))->links();

Paginator::setPageName('page_b');
$collection_B->appends('page_a', Input::get('page_a',1))->links();

In Laravel 5.2, declare the page name when using paginate().

Here is an example that works with multiple paginators on a page.

  • Be sure to specify a different $pageName for other models.

See the method \Illuminate\Database\Eloquent\Builder::paginate()

/**
 * Get things by ownerId
 *
 * @param integer $ownerId The owner ID.
 *
 * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator Returns a pagination instance.
 */
public function getThings($ownerId)
{
    $builder = \App\Models\Things::where('ownerId', '=', (integer) abs($ownerId));

    // dd([
    //     '__METHOD__' => __METHOD__,
    //     '__FILE__' => __FILE__,
    //     '__LINE__' => __LINE__,
    //     '$ownerId' => $ownerId,
    //     'sql' => $builder->toSql(),
    //     '$builder' => $builder,
    //     'paginate' => $builder->paginate($perPage = null, $columns = ['*'], $pageName = 'p', $page = null),
    // ]);

    return $builder->paginate($perPage = null, $columns = ['*'], $pageName = 'p', $page = null);
}

Note: $pageName = 'p'

$publishedArticles = Article::paginate(10, ['*'], 'published');
$unpublishedArticles = Article::paginate(10, ['*'], 'unpublished');

The third argument is used as follows:

laravel/public/articles?published=3

laravel/public/articles?unpublished=1

Comments:
2023-01-17 00:47:54
Thanks, works great for Laravel 5.4, but you still need to append other pages if you want them to work at the same time.
2023-01-17 00:47:54
That's the solution for Laravel 5.4
2023-01-17 00:47:54
Very good solution specially when using pagination without prepare directly from the the view for the related model, Suppose Customer, model that has many products, @foreach ($customer->products()->paginate(10,['*'],'anotherPageParam'‌​) as $product) And then for links, {!! $customer->products()->paginate(10,['*'],'anotherPageParam')‌​->links() !!}
2023-01-17 00:47:55
This is way more elegant and cleaner than the accepted answer, thanks!
2023-01-17 00:47:55
Works in laravel 8 as well. Thanks
2023-01-17 00:47:55
What is the second param for?

Use:

$var1 = DB1::orderBy('...')->paginate(5, ['*'], '1pagination');

$var2 = DB2::orderBy('...')->paginate(5, ['*'], '2pagination');

For Laravel 5.2

Comments:
2023-01-17 00:47:55
Duplicate answer.

In laravel 5.3+, I use paginate like this:

    $product = Product::paginate(5, ['*'], 'product');
    $region = Region::paginate(5, ['*'], 'region');

and in a blade template, I append the current page to the link of the other paginator to keep the position of both paginator when navigating:

    {{$product->appends(['region' => $region->currentPage()])->links()}}    
    {{$region->appends(['product' => $product->currentPage()])->links()}}    
Comments:
2023-01-17 00:47:55
I can confirm that this works on Laravel 5.8...just implemented it myself, thank you
2023-01-17 00:47:55
this is the correct answer in laravel 8 too

The answer of anonym may be extended to be very handy in the views that have related models. tested in :

Suppose we have a view for the CustomerController show.blade.php and we pass the Customer model to it using:

```

return view(['customer' => \App\Customer::find($customerId)]);

``` Each Customer has many Product, and has many Location and we want list with pagination both his/her Products and Locations, simply we may do it in the view like:

@foreach($customer->products()->paginate(10,['*'],'productsPage') as $product)
HTML list
@endforeach
{!! $customer->products()->paginate(10,['*'],'productsPage')->links() !!}

@foreach($customer->locations()->paginate(10,['*'],'locationsPage') as $location)
HTML list
@endforeach
{!! $customer->locations()->paginate(10,['*'],'locationsPage')->links() !!}

Laravel 5.7

Model->paginate(10, ['*'], 'paramName');

10 = Max items per page

['*'] = colums

paramName = pagination param name

Illuminate\Database\Eloquent\Builder

This is late, but it works with laravel 7.x

$messages = Message::where('status', 0)
    ->paginate(10, ['*'], 'p')
    ->appends(Arr::except(Request::query(), 'p'));

In your view, example(inbox.blade.php)

{{$messages->links()}}

Working in Laravel 8, paginate takes 4 params:

Snippet: (API Docs, Github)

public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)

Modifying the third param $pageName for each model, and appending all query params except the $pageName for that paginator should work:

FirstModel::query()
    ->paginate(null, ['*'], 'first_model')
    ->appends(request()->except('first_model'));

SecondModel::query()
    ->paginate(null, ['*'], 'second_model')
    ->appends(request()->except('second_model'));

Note, using ->appends(...) here ensures that all of the pagination links for that paginator will keep the current query param values.

For example, if you're on the url ?first_model=2&second_model=3 the links for the first_model paginator will look something like:

  • Previous: ?first_model=1&second_model=3
  • Next: ?first_model=3&second_model=3
Comments:
2023-01-17 00:47:55
Instead of ->appends, we can use ->withQueryString(). Something like this FirstModel::query() ->paginate(null, ['*'], 'first_model')->->withQueryString(). Works for me