Ajax Load More w/ Kirby Issue

Hi guys,

I have a news page setup like this in the panel.

News Page

  • Category 1
    • News Title 1
    • News Title 2
  • Category 2
    • News Title 1
    • News Title 2
  • Category 3
    • News Title 1
    • News Title 2
  • Category 4
    • News Title 1
    • News Title 2

etc…

What i’m trying to do is cap the limit to 9 articles on first page load. Then using the ajax load more, load in the rest in multiples or even just get the basic function working.

I have followed the cookbooks guide to ajax load more but Im getting the same issue as many others on this forum because im using the built-in php server which doesn’t allow dots. (I briefly looked at the drupal route fix but i don’t really know what im suppost to do with that). I don’t think it will be an issue tho considering im only using the built in server for local development and will be pushing the finished product live.

I’ve tweaked the code a little from the cookbook for news and grandChildren pages (these are the article pages).
Here is my code below.

NEWS.PHP TEMPLATE

<?php snippet('header') ?>

<main class="main" role="main">
<h1><?= $page->title()->html() ?></h1>
<?= $page->text()->kirbytext() ?>

<ul class="news" data-page="<?= $page->url() ?>" data-limit="<?= $limit ?>">

  <!-- Loop through the projects -->
  <?php foreach($news as $new): ?>
  <?php snippet('new', compact('new')) ?>
  <?php endforeach ?>

</ul>
<button class="load-more">Load more</button>
<?php snippet('footer') ?>

NEW.PHP SNIPPET

 <!-- /site/snippets/new.php -->
 <li class="new">
 <a href="<?= $new->url() ?>" class="new-link">

  <div class="new-caption">
   <h2 class="new-title"><?= $new->title()->html() ?></h2>
  </div>

 </a>
</li>

NEWS.PHP CONTROLLER

<!-- /site/controllers/news.php -->
<?php

return function($site, $pages, $page) {

  $news = $page->grandChildren()->visible();
  $count = $news->count();

  // check if the request is an Ajax request and if the limit and offset keys are set
  if(r::ajax() && get('offset') && get('limit')) {

    // convert limit and offset values to integer
    $offset = intval(get('offset'));
    $limit  = intval(get('limit'));

    // limit projects using offset and limit values
    $news = $news->offset($offset)->limit($limit);

    // check if there are more projects left
    $more = $count > $offset + 1;

  // otherwise set the number of projects initially displayed
  } else {

  $offset   = 0;
  $limit    = 2;
  $projects = $news->limit($limit);

}

  return compact('offset', 'limit', 'news', 'more');

};

NEWS.JSON.PHP TEMPLATE

<!-- /site/templates/news.json.php -->
<?php

$html = '';

foreach($news as $new) {

  // reuse the project snippet to create the HTML for each project
  // we need to set the third parameter to true, to return the
  // snippet content instead of echoing it
  $html .= snippet('new', compact('new'), true);

}

// add $html and $more to the $data array
$data['html'] = $html;
$data['more'] = $more;

// JSON encode the $data array
echo json_encode($data);

LOADMORE.JS JAVASCRIPT FILE CALLED IN FOOTER BELOW JQUERY.MIN.JS FILE

// assets/js/loadmore.js
$(function(){

  var element = $('.news');
  var url     = element.data('page') + '.json';
  var limit   = parseInt(element.data('limit'));
  var offset  = limit;

  $('.load-more').on('click', function(e) {

    $.get(url, {limit: limit, offset: offset}, function(data) {

      console.log('load more activated');

      if(data.more === false) {
        $('.load-more').hide();
      }

      element.children().last().after(data.html);

      offset += limit;

    });

  });

});

The first issue im having seems to be the lack of a limit, I can see that the limit is set to 2 inside the controller. But no limit comes into effect on initial page load. Then secondly when i push this build to the live server nothing happens on load more click. No error message (i get the usual dot error 404 on the built in php server).

I feel like im missing something obvious here but ive double checked my code for errors/spelling mistakes and it all looks gravy.

Can anyone help shed some light on this issue?

This line should be

$news = $news->limit($limit);
1 Like

Jesus, talk about missing something obvious…

Thank you. Let me try this out now.

After getting the limit to function correctly by changing the line you pointed out.

There is still no functionality coming from the load more button.

I’ve added a console log into the onclick inside the js file and its picking that up, but nothing else happens. Here is my js file again.

// assets/js/loadmore.js
$(function(){

  var element = $('.news');
  var url     = element.data('page') + '.json';
  var limit   = parseInt(element.data('limit'));
  var offset  = limit;

  $('.load-more').on('click', function(e) {

    console.log('load more activated');

    $.get(url, {limit: limit, offset: offset}, function(data) {

      if(data.more === false) {
        $('.load-more').hide();
      }

      element.children().last().after(data.html);

      offset += limit;

    });

  });

});

Could you check out the Network tab in the Develper tools and see if the XHR request gets made? And if so, what the response is?

Hi yes it looks like the xhr request is being sent, not sure how to read this correctly tho? This is what comes up when i click load more.

Is that news page your home page?

No its a normal top level page with the extension url /news

You click on the news.json request and a panel will pop up on the right. On that panel you have a Preview/Response tab. You’ll see the data returned in there. If you see the news posts returned it means that the entire XHR code is working like it should, which narrows down your problem!

Hi thanks!

It looks like preview and response contain the same code. Im not sure if all the /'s and 's are causing the issue? But here is what is inside the preview and response panel

<!-- /site/controllers/news.php -->
<!-- /site/templates/news.json.php -->
{"html":"<!-- \/site\/snippets\/new.php -->\n<li class=\"new\">\n  <a href=\"http:\/\/localhost:7000\/news\/education\/test-post-3\" class=\"new-link\">\n\n    <div class=\"new-caption\">\n      <h2 class=\"new-title\">Education Test Post 3<\/h2>\n    <\/div>\n    \n  <\/a>\n<\/li><!-- \/site\/snippets\/new.php -->\n<li class=\"new\">\n  <a href=\"http:\/\/localhost:7000\/news\/general\/general-news-1\" class=\"new-link\">\n\n    <div class=\"new-caption\">\n      <h2 class=\"new-title\">General News 1<\/h2>\n    <\/div>\n    \n  <\/a>\n<\/li>","more":true}

The presence of / and 's indicate that this HTML has been json_encode’d. You call this at the end of your news.json.php template. Maybe you need to JSON.parse the data variable from your response before trying to print it out? Try logging the data or data.html variable to the console and see if the /'s are still present there.

Hi there,

I ran in the same issue a few days ago. I was going nuts since I was doing everything as it was described in the tutorials.

As your code shows the outcommented parts from the controller and the json.php are getting written inside the final json. Deleting these two lines solved my problem there.

I hope this could help you out. Maybe it’s necessary to change it in the docs.

Greets Tobi

@ToGe88 Yeah, right, thank you, the comments were not supposed to be part of the code… I added a note in the cookbook recipe.

Hello,

im having similar problems, but when the XHR is executed upon the click function: the following error is displayed in my console:

send @ jquery.min.js:4
ajax @ jquery.min.js:4
r.(anonymous function) @ jquery.min.js:4
(anonymous) @ projects?ckcachecontrol=1512126195:101
dispatch @ jquery.min.js:3
q.handle @ jquery.min.js:3
jquery.min.js:4 XHR failed loading: GET "http://macbook-pro-9.local:5757/projects.json?limit=2&offset=2".

I’m using the latest version of the starterkit, and have followed the tutorial to a T, removing all HTML comments.

here is my code:

projects.php template:


  <main class="main" role="main">

    <header class="wrap">
      <h1><?= $page->title()->html() ?></h1>
      <div class="intro text">
        <?= $page->text()->kirbytext() ?>
      </div>
      <hr />
    </header>
      
    <ul class="projects" data-page="<?= $page->url() ?>" data-limit="<?= $limit ?>">

      <?php foreach($projects as $project): ?>
        <?php snippet('project', compact('project')) ?>
      <?php endforeach ?>

    </ul>
    <button class="load-more">Load more</button>
  </main>

<?php snippet('footer') ?>

project.php snippet:

<li class="project">
  <a href="<?= $project->url() ?>" class="project-link">
    <?php if($image = $project->image()): ?>
    <img src="<?= $image->url() ?>" alt="<?= $image->alt_text()->html() ?>" />
    <?php endif ?>
    <div class="project-caption">
      <h2 class="project-title"><?= $project->title()->html() ?></h2>
    </div>
  </a>
</li>

project.php controller

<?php

return function($site, $pages, $page) {

  $projects = page('projects')->children()->visible();
  $count = page('projects')->count();

  // check if the request is an Ajax request and if the limit and offset keys are set
  if(r::ajax() && get('offset') && get('limit')) {

    // convert limit and offset values to integer
    $offset = intval(get('offset'));
    $limit  = intval(get('limit'));

    // limit projects using offset and limit values
    $projects = $projects->offset($offset)->limit($limit);

    // check if there are more projects left
    $more = $count > $offset + 1;

  // otherwise set the number of projects initially displayed
  } else {

    $offset   = 0;
    $limit    = 2;
    $projects = $projects->limit($limit);

  }

  return compact('offset', 'limit', 'projects', 'more');

};

project.json.php


$html = '';

foreach($projects as $project) {

  $html .= snippet('project', compact('project'), true);

}

$data['html'] = $html;
$data['less'] = $more;

echo json_encode($data);

Javascript

 <?php echo js('//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js') ?>  

<script>

  var element = $('.projects');
  console.log(element);

  var json     = element.data('page') + '.json';
  console.log(json);

  var limit   = parseInt(element.data('limit'));
  console.log(limit);

  var offset  = limit;
  console.log(offset);

  $('.load-more').on('click', function(e) {
  console.log('Hurray, the button was clicked');  
    $.get(json, {limit: limit, offset: offset}, function(data) {
        console.log(data); 
      if(data.more === false) {
        $('.load-more').hide();
      }

      element.children().last().after(data.html);

      offset += limit;

    });

  });

</script>

Don’t really understand what the problem can be :pensive:
Hope you can help!

Followed to a T? Seriously?:face_with_raised_eyebrow: A very big T, probably.

Should be

$count = $projects->count();

Should be

$data['more'] = $more;

The json template should be called projects.json.php and the controller projects.php, both with an s at the end.

How embarrasing! :poop: The ‘less’ mistake was sloppy, sorry for this. and the count was something i picked up from another post. The names on the templates and controllers both were ofcourse written with an ‘S’ at the end. What made all the difference was the

$count = $projects->count();

instead of what i had.

I’m greatful for your help, and will do my best to be more attentative in the future!

It now works :pray:
Thankyou @texnixe

1 Like

@emil Well, at least I learned a new phrase…

Having butchered the phrase, i’ll do my best to avoid repeating my mistakes in the future :nerd_face:

thanks again!

I used the instructions in the cookbook. Now my old entries are showing up first.
How can I bring back the display of recent entries always first?

In controllers:

return function ($page) {

	$tag      = param('tag');
	// $default  = option('kirby.post.pagination.limit', 6);
	// $limit    = $page->limit()->or($default)->toInt();
	$articles = collection('post/articles');
	$limit    = 16;
	$articles = $page->children()->listed()->paginate($limit);

	if (empty($tag) === false) {
		$articles = $articles->filterBy('tags', $tag, ',');
	}

	return [
		'tag'      => $tag,
		// 'articles' => $articles->paginate($limit),
		'limit'      => $limit,
		'articles'   => $articles,  
		'pagination' => $articles->pagination(),  
	];

};

In collection:

return function () {
	return page('post')
		->children()
		->listed()
		->sortBy('date', 'desc');
};

Here you define your $articles:

Here you overwrite your collection, use $articles instead of redefining.