0

I'm using Laravel 5.5.* and jQuery (jquery-3.3.1.min.js).

I commercially develop mostly (like 95% of the time) in PHP, so using jQuery is really different for me, so I need help.

I am developing a blog's landing page and I must show just 3 posts in it. In it's bottom, I have a button <a> that is supposed to load 3 more posts and show it to the user. Everytime the user hits this button, 3 more posts must load in the page.

I have the following codes so far.

Posts controller

public function index() {
     // this loads the landing page with 3 initial posts
     // Working like a charm

    $posts = Posts::with('categories', 'user', 'media')
        ->where('status', 1)
        ->orderBy('published', 'desc')
        ->limit(3)
    ->get();

     $rank = self::blogPanel();

    return view('portal.pages.blog',
        compact(
            'rank',
            'posts'
        )
    );
}

I call this action from the route

Route::get('/', 'User\PostsController@index')->name('landingPage');

For the logic in which I load more posts, I have the following:

Posts Controller

public function loadMore() {
    $posts = Post::with('categories', 'user', 'media')
        ->where('status', 1)
        ->orderBy('publicacao', 'desc')
        // ->limit(3) I took this out because I was trying to set the limit in front-end
    ->get();

    return json_decode($posts);
}

Which returns the following:

array:48 [▼
  0 => {#257 ▼
    +"id": 48
    +"title": "Lorem ipsum"
    +"summary": "Perferendis labore veritatis voluptas et vero libero fuga qui sapiente maiores animi laborum similique sunt magni voluptate et."
    +"content": """
      Really long text here, with line-breaks and all
      """
    +"seo_id": null
    +"url": "Sunt rerum nisi non dolores."
    +"link_title": "dolor"
    +"published": "2018-03-01 10:35:12"
    +"scheduled": "2018-03-01 10:25:12"
    +"user_id": 1
    +"media_id": null
    +"status": 1
    +"created_at": "2018-03-01 10:25:12"
    +"updated_at": "2018-03-01 10:25:12"
    +"category_id": 3
    +"slug": "cum-aut-officia-consequatur-dolor"
    +"categories": []
    +"user": {#313 ▼
      +"id": 1
      +"name": "Jonessy"
      +"email": "jonessy@email.com"
      +"status": 1
      +"grupo_id": 1
      +"created_at": null
      +"updated_at": null
    }
    +"media": null
  }
  1 => {#341 ▶}
  2 => {#254 ▶}
]

Please, note I'm using json_decode() because it looks easier to work with in front-end.

This is my blade file, where I should print my results

blogPost.blade.php

@foreach($posts as $post)
    @php
        $date = date_create($post->published);
    @endphp
    <div class="blog-post" id="blog-post">
        <div class="blog-post__title" >
            <h3 id="artTitle">
                {{ $post->title }}
            </h3>
            @foreach($post->categories as $cat)
                <span class="blog-post__category"> {{ $cat->name }} </span>
            @endforeach
            <span class="blog-post__date" id="artDate">
                {{ date_format($date,"d/m/y - H") }}H
            </span>
        </div>
        <div class="blog-post__image" id="artImage">
            @if(isset($post->media_id))
                <img src="{{ asset('img/post-img/' . $post->media->file) }}">
            @else
                <img src="{{asset('img/post-img/default-img-post.jpg')}}">
            @endif
        </div>
        <div class="blog-post__resume">
            <p id="artSumma">
                {{ $post->summary }}
            </p>
        </div>
        <div class="blog-post__link">
            <a href="{{ route('blogPost', $post->slug) }}">
                Read More
            </a>
        </div>
        <div class="blog-post__social">
            // Some social media links for sharing
        </div>
    </div>
@endforeach

I am calling the loadMore() method from PostsController using a GET route:

Route::get('/', 'User\PostsController@loadMore')->name('loadMore');

For jQuery, here is the code I got so far:

<script type="text/javascript">
    // after importing jquery file...

    $(document).on('click', '#loadingPosts', function(e) {
        e.preventDefault();

        var listing = {!! $posts !!};
        console.log("list ", listing);

        var lastId = listing[2].id;
        console.log("id from pos 2, listing  ", lastId);

        var parts = $(listing).slice(lastId);
        console.log("part  ", parts);
        // THIS DOESN'T WORK, BY THE WAY

        // var lastId = listing[2].id;
        // console.log("listing 2 id", lastId);

        $('#loadingPosts').html("Loading More Posts");

        $.ajax({
            url: '{{ route('loadMore') }}',
            method: 'GET',
            // data: {
            //     'id': lastId,
            // I was trying to set up an ID here, but it didn't work as well
            // },
            // contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(data) {
                // console.log("checking if data not null", data);
                // this returns the expected $posts array

                $('#blog-post').html(data);
                // using .html() because .append() didn't work, which is weird, I guess
                console.log("data depois do apend", data);
                // This returns the same $posts array

                lastId = data[2].id; 
                console.log("last id from data", lastId);
                // I can retrieve the id from the given position from the array.
                // I was trying to set up the id here so I could check, with jQuery, the last post iterated, so I could continue from that point forward

                $('#loadingPosts').html("load More");

                return data[2].id;
                // A last minute despair
            }

        });
    });
</script>

Well, it doesn't work (that's the reason I'm here). I really don't know what I am doing wrong, since the $posts array is passing...

I need help with this, please.

A few things worth saying:

  • Laravel comes with a default pagination, but it works "horizontally", and the projects asks for a "vertical" pagination.

  • The page must have a "load more" button because the footer has some much needed info, so the content can not load automatically

  • IF there is a way to make it work using vanilla JavaScript OR using Laravel's PHP methods (EXCEPT FOR THE PAGINATION METHOD, AS STATED BEFORE), I would be really happy

Thank you all in advance.

G.Felicio
  • 173
  • 1
  • 1
  • 15

2 Answers2

0
public function loadMore(Request $request) {
    $posts = Post::with('categories', 'user', 'media')
        ->where('status', 1)
        ->orderBy('publicacao', 'desc')
        ->limit($request->input('limit'))
        ->skip($request->input('skip'))
    ->get();

    return json_decode($posts);
}

But you can just use the next page from pagination()

Dry7
  • 861
  • 1
  • 8
  • 17
0

So, after a little while I came up with a fix for my needs. Turns out I didn't need to json_encode() or json_decode() anything.

First of all, I'll use a pseudo mark-up for everything inside blades. It'll be easy to understand, since what I am using is HTML. For jQuery, someone involved with the project came up with a pseudo-jQuery-like functions that emulate its syntax. It is a straight forward syntax, easy to understand, nothing out of the ordinary.

Then, here it is.

PostsController

public function loadMore(Request $request) {
    $limit = $request->input('limit'); 
    $skip = $request->input('skip');
    // as @Dry7 suggested, I am taking a dynamic skip

    $posts = Post::with('categories', 'user', 'media')
        ->where('status', 1)
        ->orderBy('published', 'desc')
        ->limit($limit)
        ->skip($skip)
    ->get();

    return view(
        'portal.pages.blogPost',
        compact(
            'posts'
        )
    )->render(); // here is the difference
}

So, what I did is pre-render the view where the posts will be printed WITHOUT loading a new page.

Before we continue, here is the structure of the blog.(Using pseudo-markup, as stated before)

main page

@extends('layouts.layout')

div class=container
    div class=blog
        h1
            Page title
        /h1
        div class=blog-body
            @include('portal.pages.blogPost')

            a id=loadMorePosts class=none 
                Load More
            /a
        /div 

        div class=sidebar
            @include('portal.components.panel')
        /div
    /div   
/div

Then in my pages.blogPost I have the same code I posted in my question (The code is the one with the foreach loop).

After this, I came up with this pseudo-jQuery-like code.

// I'll start listening to any 'click' in the element I am passing the event
// then I'll increment the number of clicks  in the element
var click = 0;

// this is the first skip number
var startCounting = 6;

// start a click event in the <a #loadMorePosts> element 
$.event('#loadMorePosts','click',function () {
        // increment the number of clicks
        click++;

        // set my skip that will be sent to server and
        // applied in my PostsController
        skip = startCounting * click;

        // open an ajax request passing the route I set up
        // that calls PostsController@loadMore method
        HttpRequest.get('{{ route('loadPosts') }}?limit=6&skip=' + skip,function (res) { 
// I am concatenating my skip var here, so It'll be sent to server
            // checking if I have an empty data
            if(res.data != "") {
                // not empty, so I'll append it to my div with blog class
                // first finding the element, searching for its class
                // then passing the data to be appended
                $.append('.blog',res.data);
            } else {
                // the data is empty, so first I'll search for
                // the element with class=none
                // clean up any innerHtml it has
                // then set up a new innerHtml in it
                $.replaceAll('.none',"No More Posts");

                // to finish it all up, I style the same element with some suggesting colors and actions
                $.css('.none', 'pointer-events: none; background-color: lightgrey;');
            }
        });
    });

And its done. The posts are appended, the skip is working, so I don't take repeated posts, it works until all of my posts are loaded and when there are no more posts to show, the button is disabled, stopping any new request to be sent to server.

I hope that with these comments the process made to implement this functionality is clear and you can apply the same steps with whatever framework or library you are using.

Thank you all for reading and for taking time to answer my question.

G.Felicio
  • 173
  • 1
  • 1
  • 15