I can't add item(s) to the end of collection

Hi there,

I am getting quite desperate here. I can’t for the life of me figure out how to add an item to the end of a collection. I thought that is what append() is for, but I can’t get it to work. I tried add() and extend() as well, they don’t work either.

What I need to do is to (additionally) add a collection’s first and second item to the collections end again.

Trying just to add the first item again, I tried:

$myFiles = page('landingpage')->files()->template('media')->sortBy('sort');
$myFiles->append($myFiles->first());

That does not work. What am I doing wrong?

Thanks!

How I see it, you are not creating a collection with that code. I think is similar to this other post what you want.

In the linked question the issue is mainly that the user wants to merge collections of different types. Surely there must be an easier way to add an item of the same type to the end of a collection?

Btw., the prepend() method works as I expected:

$myFiles = page('landingpage')->files()->template('media')->sortBy('sort');
$myFiles->prepend($myFiles->last()); // adds the last file to the beginning of the collection

so I don’t understand why the append method seems to fail altogether?

Because it is not a collection. You can create a collection with new Collection() I don’t know the rest of your code but maybe something like

$myFiles = new Collection(page('landingpage')->files()->template('media')->sortBy('sort');); // here we create a collection

You can try new Pages() too, the docs say it creates a collection

What is not a collection?

$myFiles = page('landingpage')->files()->template('media')->sortBy('sort');

creates a files object that extends the general collection class and should inherit all the collection class methods. That’s why prepend() works for example. So why does append() not work?

That does not solve the problem. If I do

    $slides = new Collection(page('landingpage')->files()->template('media')->sortBy('sort'));
    $slides->append($slides->first());

it still does not add anything to my collection.

I am working with files here, not with pages, but when I use the new Files() method instead and follow the advice from the other thread, it also does not work:

    $files1 = new Files(page('landingpage')->files()->template('media')->sortBy('sort'));
    $files2 = new Files(page('landingpage')->files()->template('media')->sortBy('sort')->limit(2));

    $merged = $files1->add($files2);  // still only has the length and content of $files1

Thinking in what you are tryin to do, maybe it’s because it is already on the collection so you can not add it again? maybe there is another approach to what you want to do? Why do you need to add the same files again?

Well, if that were the case, I think this should be considered a bug. I mean, why would a command like like append() not allow to add items that are already in the collection? Especially when prepend() does it just fine.

Because I need them to appear at the end of a slideshow again to enable a seemless rollover back to the start of the slideshow. I currently “solving” this by using two separate loops, but that can’t be the right way to do it, so I would still like to know what’s would be the designated way of doing this in Kirby (and also why append() and add() don’t work as expected).

I doubt that prepend works. You can’t have the same key twice, that’s why none of these will work.

You could solve this with an array of files.

$newFiles = [];
$files = page('photography')->children()->first()->files();
foreach ($files as $file) {
  $newFiles[] = $file;
}
$newFiles[] = $files->first();

foreach ($newFiles as $file) {
  echo $file->filename() . '<br>';
}

But you might as well do your loop and then render the first file again, instead of trying to create a collection. I don’t see why you would need two loops.

Well, I have successfully used this prepend method to duplicate the last file for a few hours now. So is the bug rather in the prepend() function then? :wink:

I need to add the first two elements of the collection to the end, so I was using the second loop to again loop over the files collection with a limit(2) option.

So the easiest way to create this collection I came up with now was this:

  <?php
    $newFiles = [];
    $files = page('landingpage')->files()->template('media')->sortBy('sort');
    $files->prepend($files->last());
    foreach ($files as $file) {
      $newFiles[] = $file;
    }
    $newFiles[] = $files->first();
    $newFiles[] = $files->nth(1);
  ?>

This still seems incredibly cumbersome, just to add a few items to a collection/array. I guess I will just stick to my 2-loop-solution for now, all in all that seems cleaner to me.

Interesting, I tested similar code without success. Which Kirby version are you using? The file is put at the beginning but not duplicated inside the collection.

3.2.3

For me it duplicates in the collection and I can then access its url(). Of course it does not duplicate physically in the contents folder.

no, of course not.

I’m testing in 3.3.2 with this code

$files = page('photography')->children()->first()->files();
dump($files);

// result
/*
Kirby\Cms\Files Object
(
    [0] => photography/sky/blood-moon.jpg
    [1] => photography/sky/coconut-milkyway.jpg
    [2] => photography/sky/dark-forest.jpg
    [3] => photography/sky/desert-tree.jpg
    [4] => photography/sky/tent-in-the-woods.jpg
)
*/
$files = $files->prepend($files->first());
dump($files);
/*
Kirby\Cms\Files Object
(
    [0] => photography/sky/blood-moon.jpg
    [1] => photography/sky/coconut-milkyway.jpg
    [2] => photography/sky/dark-forest.jpg
    [3] => photography/sky/desert-tree.jpg
    [4] => photography/sky/tent-in-the-woods.jpg
)


*/

The number of files and the files themselves are exactly the same.

Hm. It seems to work somehow when I prepend the last item, but I admit I don’t understand why.

I basically get this:

$slides = page('landingpage')->files()->template('media')->sortBy('sort');
dump($slides);

// result
/*
Kirby\Cms\Files Object
(
  [0] => landingpage/someVideo.mp4
  [1] => landingpage/testgrafik.jpg
  [2] => landingpage/image_02.jpg
  [3] => landingpage/image_03.jpg
)
*/

$slides->prepend($slides->last());
dump($slides);
/*
Kirby\Cms\Files Object
(
  [0] => 0
  [1] => landingpage/someVideo.mp4
  [2] => landingpage/testgrafik.jpg
  [3] => landingpage/image_02.jpg
  [4] => landingpage/image_03.jpg
)
*/

So I get a 0 in the first position. But strangely, when I insert this into my template like this

      <?php $ix = 0; ?>
      <?php foreach ($slides as $slide): ?>
        {name: 'rs<?= $ix ?>',
         url:  '<?= $slide->url() ?>'},
         <?php $ix++ ?>
      <?php endforeach ?>

it still correctly extracts the url of the last file? How does that work?

Anyways, so the gist of this all is that prepend() and append() can not generally be used to add duplicate items to a list, I guess?

Excactly. That’s why I would go with either the array solution I suggested above or by simply looping through the files collection and then render the first item again.

<?php $slides = page('landingpage')->files()->template('media')->sortBy('sort'); ?>
<?php if ($slides->isNotEmpty()): ?>
    <?php foreach ($slides as $slide) : ?>
        <img src="<?= $slide->url() ?>">
    <?php endforeach ?>
    <img src="<?= $slides->first()->url() ?>">
<?php endif ?>

Yes. That just makes the code really messy in my specific case, as I need to add the last item in the beginning as well as the first two items in the end, but I guess I will have to do it this way.

It would be nice, if Kirby allowed to add duplicates to a collection, but I understand that this is probably a limitation of php arrays and probably also not a requirement that pops up all too often.

One last thing I would like to mention though is that it would be nice, if the prepend() and append() method would produce some sort of error instead of just silently fail in such a case.

Don’t know if you will find this less messy, but you could create an array of files-objects and loop through this array and within the first loop through the objects:

$lastitem = page('photography')->children()->first()->files()->slice(-1,1);
$allitems = page('photography')->children()->first()->files();
$first2items = page('photography')->children()->first()->files()->slice(0,2);

dump($allitems);
/*
Kirby\Cms\Files Object
(
    [0] => photography/trees/cheesy-autumn.jpg
    [1] => photography/trees/last-tree-standing.jpg
    [2] => photography/trees/monster-trees-in-the-fog.jpg
    [3] => photography/trees/sharewood-forest.jpg
    [4] => photography/trees/stay-in-the-car.jpg
)
*/
foreach ([$lastitem,$allitems,$first2items] as $files) {
  foreach ($files as $file) {
    dump($file->id());
  }
}
/*
photography/trees/stay-in-the-car.jpg
photography/trees/cheesy-autumn.jpg
photography/trees/last-tree-standing.jpg
photography/trees/monster-trees-in-the-fog.jpg
photography/trees/sharewood-forest.jpg
photography/trees/stay-in-the-car.jpg
photography/trees/cheesy-autumn.jpg
photography/trees/last-tree-standing.jpg
*/

Thank you @Adspectus, I find this cleaner indeed and this is now how I set up my loop. :+1: