Home  >  Q&A  >  body text

Why are AJAX calls failing in this Laravel application?

I'm developing a blog application in Laravel 8.

ArticlesController Controller I have this method to display a single article and its Comments:

class ArticlesController extends FrontendController {

    // More code

    public function show($slug) {
        // Single article
        $article = Article::firstWhere('slug', $slug);
        $old_article = Article::where('id', '<', $article->id)->orderBy('id', 'DESC')->first();
        $new_article = Article::where('id', '>', $article->id)->orderBy('id', 'ASC')->first();

        // Comments
        $commentsQuery = Comment::where(['article_id' => $article->id, 'approved' => 1])->orderBy('id', 'desc');
        $comments = $commentsQuery->paginate(10);
        $comments_count = $commentsQuery->count();

        return view('themes/' . $this->theme_directory . '/templates/single', 
            array_merge($this->data, [
                'categories' => $this->article_categories,
                'article' => $article,
                'old_article' => $old_article,
                'new_article' => $new_article,
                'comments' => $comments,
                'comments_count' => $comments_count,
                'tagline' => $article->title,
                ])
            );
    }

}

In the view I have a list of comments like this:

<div id="commentsList">
  <ol class="commentlist {{ boolval($is_infinitescroll) ? 'infinite-scroll' : '' }}">
    @foreach ($comments as $comment)
    <li class="depth-1 comment">
      <div class="comment__avatar">
        <img class="avatar" src="{{ asset('images/avatars/' . $comment->user->avatar) }}" alt="" width="50" height="50">
      </div>
      <div class="comment__content">
        <div class="comment__info">
          <div class="comment__author">{{ $comment->user->first_name }} {{ $comment->user->last_name }}</div>
          <div class="comment__meta">
            <div class="comment__time">{{ date('jS M Y', strtotime($comment->created_at)) }}</div>
            <div class="comment__reply">
              <a class="comment-reply-link" href="#0">Reply</a>
            </div>
          </div>
        </div>
        <div class="comment__text">
          <p>{{ $comment->body }}</p>
        </div>
      </div>
    </li>
    @endforeach
  </ol>
  
  <div class="ajax-load text-center is-hidden">
    loading...
  </div>
</div>

Routes related to the article:

// Article routes
Route::get('/', [ArticlesController::class, 'index'])->name('homepage');
Route::get('/category/{category_id}', [ArticlesController::class, 'category'])->name('category');
Route::get('/author/{user_id}', [ArticlesController::class, 'author'])->name('author');
Route::get('/show/{slug}', [ArticlesController::class, 'show'])->name('show');

Target

I want to replace comment pagination with "infinite scroll".

For this purpose I have:

/* Infinite comments */
function infiniteComments() {
    var page = 1;
    $(window).scroll(function() {
      if ($(window).scrollTop() + $(window).height() >= $(document).height() - $('.s-footer').height()) {
        page++;
        loadMoreData(page);
      }
    });
  }

  function loadMoreData(page){
    var base_url = window.location.href.split('?')[0];
    $.ajax({
        url: `${base_url}?page=${page}`,
        type: "get",
        beforeSend: function() {
          $('.ajax-load').show();
        }
      })
      .done(function(data) {
        if (data.html == "") {
          $('.ajax-load').hide();
          return;
        }
        $('.ajax-load').hide();
        $(".infinite-scroll").append(data.html);
      })
      .fail(function(jqXHR, ajaxOptions, thrownError) {
        console.log('The server is not responding...');
      });
 }

 $(document).ready(function(){
    infiniteComments();
 });

question

When visiting https://larablog.com/show/deserunt-qui-exeritationem?page=2 the comments on page 2 are displayed correctly, the Chrome console shows these 500 (internal Server Error)Error:

https://larablog.com/show/deserunt-qui-exercitationem?page=65 500 (Internal Server Error)
The server is not responding...

https://larablog.com/show/deserunt-qui-exercitationem?page=76 500 (Internal Server Error)
The server is not responding...

The error can be traced to the error message on line 70 in ArticlesController - $article = Article::firstWhere('slug', $slug):

Try to get the attribute "id" of a non-object.

This is weird because $article = Article::firstWhere('slug', $slug) works fine without Ajax.

question

  1. What causes this error?
  2. What's the easiest fix?

P粉779565855P粉779565855219 days ago267

reply all(2)I'll reply

  • P粉253518620

    P粉2535186202024-02-18 10:55:43

    firstWhere Returns the first record that meets the delivery conditions. The default is null. So, your line

    $article = Article::firstWhere('slug', $slug);

    Will return the first article that slug matches $slug, or null if no such record exists. Now, whenever you quote $article->id, you assume that $article is a correct Article and you want to know its The value of id. If there are no matching articles, this will produce the error you encountered.

    Therefore, it is wise to check for empty($article) after $article is initialized, and handle edge cases when it is indeed empty.

    reply
    0
  • P粉121447292

    P粉1214472922024-02-18 10:00:05

    This is my solution:

    Added this new route in routes\web.php:

    Route::post('/load_comments', [ArticlesController::class, 'get_comments_ajax'])->name('load_comments');

    In ArticlesController:

    /**
     * AJAX Call for Loading extra comments
     *
     * @param Request $request
     *
     * @return void
     */
    public function get_comments_ajax( Request $request ) {
        if ( ! $request->ajax() ) {
            // Redirect to Home Page or just BOMB OUT!
            exit();
        }
    
        $more_comments_to_display = TRUE;
    
        /** @todo - 5 - This should\could be a setting */
    
        $article_id  = $request->post( 'article_id' );
        $page_number = $request->post( 'page' );
        $offset      = $this->comments_per_page * $page_number;
    
        $data['comments'] = $this->get_commentQuery( $article_id, $this->comments_per_page, $offset )->get();
        $content          = '';
        if ( $data['comments']->count() ) {
            $content .= view('themes/' . $this->theme_directory . '/partials/comments-list',
                array_merge( $data, [
                  'is_infinitescroll' => $this->is_infinitescroll
                ])
            );
        } else {
            $more_comments_to_display = FALSE;
        }
        echo json_encode( [ 'html' => $content, 'page' => $page_number, 'more_comments_to_display' => $more_comments_to_display, 'article_id' => $article_id ] );
        exit();
    }
    
    /**
     * get_commentQuery
     *
     * @param int $article_id
     * @param int $limit
     * @param int $offset
     *
     * @return object
     */
    private function get_commentQuery( int $article_id, int $limit = 0, int $offset = 0 ): object {
        $commentQuery = Comment::where( [ 'article_id' => $article_id, 'approved' => 1 ] )->orderBy( 'id', $this->comments_orderby_direction );
        if ( $offset > 0 ) {
            $commentQuery = $commentQuery->offset( $offset );
        }
        if ( $limit > 0 ) {
            $commentQuery = $commentQuery->limit( $limit );
        }
    
        return $commentQuery;
    }

    I will only load the Ajax script if there are more than 10 comments:

    @if ($is_infinitescroll && $comments_count > $comments_per_page)
        @section('custom_js_files')
            sssccc
        @endsection
    @endif

    script:

    $(document).ready(function () {
    
        let flagMoreCommentsToDisplay = true;
        let flagCommentsBlockNewRequest = false;
        let domInfiniteScroll = $(".infinite-scroll");
    
        infiniteComments();
    
        function infiniteComments() {
            let page = 0;
            $(window).scroll(function () {
                if (flagCommentsBlockNewRequest === false) {
                    if ($(window).scrollTop() + $(window).height() >= $(document).height() - $('.s-footer').height()) {
                        if (flagMoreCommentsToDisplay) {
                            flagCommentsBlockNewRequest = true;
                            page++;
                            loadMoreData(page);
                        }
                    }
                }
            });
        }
    
        function loadMoreData(page) {
            let base_url = window.location.origin
            $.ajax({
                url: base_url + '/load_comments',
                type: 'POST', dataType: 'json',
                data: {'_token': token, 'page': page, 'article_id': article_id},
                beforeSend: function () {
                    $('.ajax-load').show();
                }
            })
            .done(function (data) {
                $('.ajax-load').hide();
                let commentHtml = data.html;
                flagMoreCommentsToDisplay = data.more_comments_to_display;
                if (flagMoreCommentsToDisplay) {
                    if (commentHtml !== '') {
                        domInfiniteScroll.append(commentHtml);
                    }
                }
                flagCommentsBlockNewRequest = false;
            })
            .fail(function () {
                flagCommentsBlockNewRequest = false;
            });
        }
    });

    reply
    0
  • Cancelreply