Laravel 5 Paginate + Infinite Scroll jQuery
Asked Answered
I

2

6

I am trying to use paginate() to achieve infinite scroll. I think the easiest way is using the 'infinite-scroll' to achieve this. If you have any other suggestion how to do it without infinite-scroll library, just using jQuery, I'd be happy to know..

I am returning the variable to view like this:

public function index()
 {
    $posts = Post::with('status' == 'verified')
                      ->paginate(30);

    return view ('show')->with(compact('posts'));
 }

My View:

<div id="content" class="col-md-10">
    @foreach (array_chunk($posts->all(), 3) as $row)
        <div class="post row">
            @foreach($row as $post)
                <div class="item col-md-4">
                    <!-- SHOW POST -->
                </div>
            @endforeach
        </div>
    @endforeach
    {!! $posts->render() !!}
 </div>

Javascript Part:

$(document).ready(function() {
  (function() {
     var loading_options = {
        finishedMsg: "<div class='end-msg'>End of content!</div>",
        msgText: "<div class='center'>Loading news items...</div>",
        img: "/assets/img/ajax-loader.gif"
     };

     $('#content').infinitescroll({
         loading: loading_options,
         navSelector: "ul.pagination",
         nextSelector: "ul.pagination  li:last a",   // is this where it's failing?
         itemSelector: "#content div.item"
     });
   });
}); 

However, this doesn't work. The ->render() part is working because I am getting [<[1]2]3]>] part. However, the infinite scroll doesn't work. I also don't get any errors in the console.

[<[1]2]3]>] is like this in the view:source:

<ul class="pagination">
       <li class="disabled"><span>«</span> </li>                    //   «
       <li class="active"><span>1</span></li>                       //   1
       <li><a href="http://test.dev/?page=2">2</a></li>             //   2
       <li><a href="http://test.dev/?page=3">3</a></li>             //   3
       <li><a href="http://test.dev/?page=2" rel="next">»</a></li>  //   »
</ul>
Icecap answered 19/10, 2015 at 22:50 Comment(4)
I'll admit, I'm unfamiliar with the infinitescroll plugin, but I have done some of an infinity scroll that I built my own. I saved the pages variable in a hidden input box and when you scroll to the bottom, the page count gets updated and the function that pulls the new entries based on the new page number gets called and appends the new entries to the bottom of the container. I also had a set a max_pages hidden input box so when the user hits that max_page count, you see a message like end of page. I don't know if that's the best way, but it worked best for me. Would you like to see some exp?Allethrin
I am open to any suggestions as long as I can use a loading text/image too (so having function for loading state). I think it's better to do it yourself rather than using a library, but I think I'd love to stick up with Laravel's paginate(). I'd love to see your method. Can you please adapt it in my case?Icecap
Of course! View my answer below. :)Allethrin
Here is a related question: https://mcmap.net/q/956370/-laravel-and-infinite-scroll/470749Represent
A
2

You should be able to use the Pagination just fine as long as your call to get new posts is different than page load. So you'd have two Laravel calls:

1.) To provide the template of the page (including jQuery, CSS, and your max_page count variable -- view HTML) 2.) For the AJAX to call posts based on the page you give it.

This is how I got my infinity scroll to work...

HTML:

<!-- Your code hasn't changed-->
<div id="content" class="col-md-10">
  @foreach (array_chunk($posts->all(), 3) as $row)
    <div class="post row">
        @foreach($row as $post)
            <div class="item col-md-4">
                <!-- SHOW POST -->
            </div>
        @endforeach
    </div>
  @endforeach
  {!! $posts->render() !!}
</div>

<!-- Holds your page information!! -->
<input type="hidden" id="page" value="1" />
<input type="hidden" id="max_page" value="<?php echo $max_page ?>" />

<!-- Your End of page message. Hidden by default -->
<div id="end_of_page" class="center">
    <hr/>
    <span>You've reached the end of the feed.</span>
</div>

On page load, you will fill in the max_page variable (so do something like this: ceil(Post::with('status' == 'verified')->count() / 30);.

Next, your jQuery:

var outerPane = $('#content'),
didScroll = false;

$(window).scroll(function() { //watches scroll of the window
    didScroll = true;
});

//Sets an interval so your window.scroll event doesn't fire constantly. This waits for the user to stop scrolling for not even a second and then fires the pageCountUpdate function (and then the getPost function)
setInterval(function() {
    if (didScroll){
       didScroll = false;
       if(($(document).height()-$(window).height())-$(window).scrollTop() < 10){
        pageCountUpdate(); 
    }
   }
}, 250);

//This function runs when user scrolls. It will call the new posts if the max_page isn't met and will fade in/fade out the end of page message
function pageCountUpdate(){
    var page = parseInt($('#page').val());
    var max_page = parseInt($('#max_page').val());

    if(page < max_page){
       $('#page').val(page+1);
       getPosts();
       $('#end_of_page').hide();
    } else {
      $('#end_of_page').fadeIn();
    }
}


//Ajax call to get your new posts
function getPosts(){
    $.ajax({
        type: "POST",
        url: "/load", // whatever your URL is
        data: { page: page },
        beforeSend: function(){ //This is your loading message ADD AN ID
            $('#content').append("<div id='loading' class='center'>Loading news items...</div>");
        },
        complete: function(){ //remove the loading message
          $('#loading').remove
        },
        success: function(html) { // success! YAY!! Add HTML to content container
            $('#content').append(html);
        }
     });

} //end of getPosts function

There ya go! That's all. I was using Masonry with this code also so the animation worked wonderfully.

Allethrin answered 20/10, 2015 at 16:11 Comment(11)
Thanks for your answer. It's really nicely explained. I adapted your code to mine however, I ended up with Uncaught SyntaxError: Unexpected token ). I believe it's because I'm trying ajax post without csrf token. I tried adding var currentPage = {'page': $('#page').val(), "_token": "{{{ csrf_token() }}}"}; and changing data: { page: page } to data:currentPage, as well as data: { page: currentPage }, but couldn't make it work.Icecap
Unexpected token error usually means there's a syntax error. Did you check to make sure all your parenthesis are closed properly? Are you using Laravel 5 also?Allethrin
Yes, I'm using L5. I found the problem and fixed in in terms of syntax. But now, I still see [<[1]2]3]>] at the end of the page as well as 'You've reached the end of the feed.'. Now, I started getting TokenMismatchException in VerifyCsrfToken.php even though I tried {'page': $('#page').val(), "_token": "{{{ csrf_token() }}}"};.. What I exactly did after fixing the syntax errors: changed to var currentPage = {'page': page, "_token": "{{{ csrf_token() }}}"} and data: currentPage,Icecap
You're still getting the [<1, 2, 3 >] because of this line: ` {!! $posts->render() !!}` . It spits out the pagination links according to the documentation (it's different in 4.2). Are you calling this jQuery function within the view?Allethrin
thanks for your detailed answer. I finally made it work. I wish I could give you +10 points :) However, I have a problem now. When I come to the end of the page for the first time, it calls the ajax twice, and whatever I tried, I couldn't figure out why. As soon as I hit bottom, it echoes 2x Loading please wait text in a blink of an eye. Maybe it's related with setInterval, 250, because it's more like behaving right when I change 250 to 5000, but I'm not sure if it's a good practice. Do you have any suggestions?Icecap
Phew! Glad you got it to work! I was going to ask you if you somehow managed to get it. If you up vote any of my comments, that adds points as well. :) Regarding the ajax getting called twice, it means the setInterval function is placed in an area that the interval gets set twice. This depends on how you have things setup. Make sure the jQuery isn't in the view that continuously gets called. This will reset the Interval when you scroll and the page count updates each time. Try placing it in the template (on page load).Allethrin
I think it's because of the line var outerPane = $('#content'), I couldn't figure out what to do with itIcecap
Hmm. I don't believe it's that. When things get duplicated it usually means that the function that fires it gets initialized twice. Sometimes, having the jQuery that fires the functions should only live on the template page and not in an external. It just sometimes requires the perfect combination.Allethrin
I fixed my issue as adding a boolean var availableToLoad = true; and getting rid of setInterval :) Thanks a lot mateIcecap
Way to think out of the box! ;) You're welcome! Glad you got it.Allethrin
Hi. Thanks for this well-explained answer, however, the output is not giving me infinity scroll.Panfish
S
5

Easy and helpful is this tutorial - http://laraget.com/blog/implementing-infinite-scroll-pagination-using-laravel-and-jscroll

Final script could looks like this one

{!! HTML::script('assets/js/jscroll.js') !!}
<script>
    $('.link-pagination').hide();
    $(function () {
        $('.infinite-scroll').jscroll({
            autoTrigger: true,
            loadingHtml: '<img class="center-block" src="/imgs/icons/loading.gif" alt="Loading..." />', // MAKE SURE THAT YOU PUT THE CORRECT IMG PATH
            padding: 0,
            nextSelector: '.pagination li.active + li a',
            contentSelector: 'div.infinite-scroll',
            callback: function() {
                $('.link-pagination').remove();
            }
        });
    });
</script>

You just need to use laravel's pagination

{!! $restaurants->links() !!}
Subalternate answered 30/3, 2017 at 9:36 Comment(1)
works well, what if I need to use a manual trigger like "load more" insteadPilsudski
A
2

You should be able to use the Pagination just fine as long as your call to get new posts is different than page load. So you'd have two Laravel calls:

1.) To provide the template of the page (including jQuery, CSS, and your max_page count variable -- view HTML) 2.) For the AJAX to call posts based on the page you give it.

This is how I got my infinity scroll to work...

HTML:

<!-- Your code hasn't changed-->
<div id="content" class="col-md-10">
  @foreach (array_chunk($posts->all(), 3) as $row)
    <div class="post row">
        @foreach($row as $post)
            <div class="item col-md-4">
                <!-- SHOW POST -->
            </div>
        @endforeach
    </div>
  @endforeach
  {!! $posts->render() !!}
</div>

<!-- Holds your page information!! -->
<input type="hidden" id="page" value="1" />
<input type="hidden" id="max_page" value="<?php echo $max_page ?>" />

<!-- Your End of page message. Hidden by default -->
<div id="end_of_page" class="center">
    <hr/>
    <span>You've reached the end of the feed.</span>
</div>

On page load, you will fill in the max_page variable (so do something like this: ceil(Post::with('status' == 'verified')->count() / 30);.

Next, your jQuery:

var outerPane = $('#content'),
didScroll = false;

$(window).scroll(function() { //watches scroll of the window
    didScroll = true;
});

//Sets an interval so your window.scroll event doesn't fire constantly. This waits for the user to stop scrolling for not even a second and then fires the pageCountUpdate function (and then the getPost function)
setInterval(function() {
    if (didScroll){
       didScroll = false;
       if(($(document).height()-$(window).height())-$(window).scrollTop() < 10){
        pageCountUpdate(); 
    }
   }
}, 250);

//This function runs when user scrolls. It will call the new posts if the max_page isn't met and will fade in/fade out the end of page message
function pageCountUpdate(){
    var page = parseInt($('#page').val());
    var max_page = parseInt($('#max_page').val());

    if(page < max_page){
       $('#page').val(page+1);
       getPosts();
       $('#end_of_page').hide();
    } else {
      $('#end_of_page').fadeIn();
    }
}


//Ajax call to get your new posts
function getPosts(){
    $.ajax({
        type: "POST",
        url: "/load", // whatever your URL is
        data: { page: page },
        beforeSend: function(){ //This is your loading message ADD AN ID
            $('#content').append("<div id='loading' class='center'>Loading news items...</div>");
        },
        complete: function(){ //remove the loading message
          $('#loading').remove
        },
        success: function(html) { // success! YAY!! Add HTML to content container
            $('#content').append(html);
        }
     });

} //end of getPosts function

There ya go! That's all. I was using Masonry with this code also so the animation worked wonderfully.

Allethrin answered 20/10, 2015 at 16:11 Comment(11)
Thanks for your answer. It's really nicely explained. I adapted your code to mine however, I ended up with Uncaught SyntaxError: Unexpected token ). I believe it's because I'm trying ajax post without csrf token. I tried adding var currentPage = {'page': $('#page').val(), "_token": "{{{ csrf_token() }}}"}; and changing data: { page: page } to data:currentPage, as well as data: { page: currentPage }, but couldn't make it work.Icecap
Unexpected token error usually means there's a syntax error. Did you check to make sure all your parenthesis are closed properly? Are you using Laravel 5 also?Allethrin
Yes, I'm using L5. I found the problem and fixed in in terms of syntax. But now, I still see [<[1]2]3]>] at the end of the page as well as 'You've reached the end of the feed.'. Now, I started getting TokenMismatchException in VerifyCsrfToken.php even though I tried {'page': $('#page').val(), "_token": "{{{ csrf_token() }}}"};.. What I exactly did after fixing the syntax errors: changed to var currentPage = {'page': page, "_token": "{{{ csrf_token() }}}"} and data: currentPage,Icecap
You're still getting the [<1, 2, 3 >] because of this line: ` {!! $posts->render() !!}` . It spits out the pagination links according to the documentation (it's different in 4.2). Are you calling this jQuery function within the view?Allethrin
thanks for your detailed answer. I finally made it work. I wish I could give you +10 points :) However, I have a problem now. When I come to the end of the page for the first time, it calls the ajax twice, and whatever I tried, I couldn't figure out why. As soon as I hit bottom, it echoes 2x Loading please wait text in a blink of an eye. Maybe it's related with setInterval, 250, because it's more like behaving right when I change 250 to 5000, but I'm not sure if it's a good practice. Do you have any suggestions?Icecap
Phew! Glad you got it to work! I was going to ask you if you somehow managed to get it. If you up vote any of my comments, that adds points as well. :) Regarding the ajax getting called twice, it means the setInterval function is placed in an area that the interval gets set twice. This depends on how you have things setup. Make sure the jQuery isn't in the view that continuously gets called. This will reset the Interval when you scroll and the page count updates each time. Try placing it in the template (on page load).Allethrin
I think it's because of the line var outerPane = $('#content'), I couldn't figure out what to do with itIcecap
Hmm. I don't believe it's that. When things get duplicated it usually means that the function that fires it gets initialized twice. Sometimes, having the jQuery that fires the functions should only live on the template page and not in an external. It just sometimes requires the perfect combination.Allethrin
I fixed my issue as adding a boolean var availableToLoad = true; and getting rid of setInterval :) Thanks a lot mateIcecap
Way to think out of the box! ;) You're welcome! Glad you got it.Allethrin
Hi. Thanks for this well-explained answer, however, the output is not giving me infinity scroll.Panfish

© 2022 - 2024 — McMap. All rights reserved.