Ajax load more - shuffle content only once

Hello,

I followed the ajax load more tutorial and everything works.
I am just wondering if there is way to shuffle the content only once and then ajax load the shuffled content. With the following code the content gets shuffled on every ajax load.

<!-- controller from the ajax load more tutorial -->

<!-- /site/controllers/projects.php -->

<?php

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

$projects = $page->children()->visible()->shuffle();

[...]

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

};

Try to wrap the first line in a if statement

if(!r::ajax()) {
  $projects = $page->children()->visible()->shuffle();
}

unfortunately it does not work. It does not load any content anymore then. Is there a way to globally declare the variable once, maybe outside of the return function, and then pass it to the return function on every ajax load more ?

Hm, ok, yeah, makes sense. Another idea: Pass the resulting $projects variable to a data attribtute to send it to the Javascript and then again back to the controller.

Isn’t it only possible to pass arguments from the router to the controller? I found this in the docs of the controller:

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

  // $args contains all arguments from the current route

};

So i tried out to pass the variable from the router to the controller like this:

c::set('routes', array(
  array(
   'pattern' => 'localhost/kirby_cms/home/',
   'action' => function () {
       $projects = $pages->find('projects')->children()->children()->filter(function($p) {return $p->social_w() == '1';})->sortBy('sort','desc')->shuffle();
        return $projects;
    },
    'method' => 'GET|POST',
  ),
));

and receive it in the controller like this:

<?php
  return function($site, $pages, $page, $args) {
    $projects = get('projects');
    $count    = $projects->count();
  if(r::ajax() && get('offset') && get('limit')) {
    $offset = intval(get('offset'));
    $limit  = intval(get('limit'));
    $projects = $projects->offset($offset)->limit($limit);
    $more = $count > $offset + 1;
  } else {
    $offset   = 0;
    $limit    = 4;
    $projects = $projects->limit($limit);
  }
  return compact('offset', 'limit', 'projects', 'more', 'name');
};

But it does not seem to pass the variable, what am I doing wrong ?

Well, first of all, your route has to return the page together with a data array, and your routing pattern is not correct, either.

But while this would work to give you the projects if set up correctly, it doesn’t help you any, because next time the controller is called via the Ajax call, the information will be lost.

Getting back to my last suggestion:

Controller:

<?php

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

  // shuffle the collection
  $projects = $page->children()->visible()->shuffle();
  // pass all uids of the shuffled collection into an array
  foreach($projects as $p) {
    $data[] = $p->uid();
  }
  $count    = $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')) {
    // get the data from the Ajax request
    $collection = get('projects');
    // create a new Pages object
    $projects = new Pages();
    // loop through the uids, get each page and add it to the $projects collection, restores the original shuffled collection
    foreach($collection as $item) {
      $projects->add(page('projects')->children()->findBy('uid', $item));
    }
    $offset = intval(get('offset'));
    // convert limit and offset values to integer
    $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', 'projects', 'firstname', 'data');

};

Template: Add data-projects attribute

<ul class="projects" data-page="<?= $page->url() ?>" data-limit="<?= $limit ?>" data-projects='<?= json_encode($data) ?>'>

Script:

$(function(){

  var element = $('.projects');
  var url     = element.data('page') + '.json';
  var limit   = parseInt(element.data('limit'));
  var offset  = limit;
  var projects = element.data('projects');
  $('.load-more').on('click', function(e) {

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

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

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

      offset += limit;

    });

  });

});

Edit: While this works, it’s probably not such a great idea with hundred of project titles printed out into the data attribute.

I just had another idea, how about storing the shuffled order in a field in the project files:

Controller:

<?php

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

  $projects = page('projects')->children()->sortBy('shuffleorder', 'asc');

  $count    = $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);
    //dump($projects);
    // check if there are more projects left
    $more = $count > $offset + 1;

  // otherwise set the number of projects initially displayed
  } else {
    $projects = $projects->shuffle();
    $sort = 1;
    foreach($projects as $p) {
      $p->update(array(
        'shuffleorder' => $sort
      ));
      $sort++;
    }
    $offset   = 0;
    $limit    = 2;
    $projects = $projects->limit($limit);

  }

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

};

Don’t know how this affects performance if you have to update hundreds of files all the time…

I tried the first and second solution. The first did it for me. No performance issues. Thank you very much.