Sorting by complex function on panel pages

Hello,
On the frontend I am sorting $page->children() by using a ‘complex’ function as a custom page method, and I would like to sort the same pages in a pages section in the panel using the same or equivalent function which in pseudocode it goes like this:

create new pages collection
add all children who have field 'pinned' as 'true', sorted by field 'publicationDate' descending
add all children who have field 'pinned' as 'false', sorted by field 'publicationDate' descending
return the collection

According to the reference the sortBy option of pages section allows only for a field, and nothing else.

Could I extend the pages section in order to add a custom sorting option that uses my custom page method?

If yes, where can I find the pages section code ?

Thank you

Your custom method should then do the job, no?

sortBy: customethodname

The method extends $page, so I call it as $page->customMethod()

You mean I can just do as you suggest on the pages section and it will use the actual page where the pages section is in place of $this? and then it will load the custom collection ?

sortBy: customethodname

Yes, that’s what I meant

Does not seem to work.

This is the full blueprint at
site/pages/programme.yml

I am using the method below the ‘updates’ tab and the ‘public’ pages section.

title: Programme

tabs:
  info:
    label: Info
    icon: text
    fields:
      info:
        type: textarea
        label: Info text
        size: large
        autofocus: false
        buttons:
            - link
            - email
      publicationDate:
        type: hidden

  updates:
    label: Updates
    icon: funnel
    columns:
      - width: 1/2
        sections:
          draft:
            type: pages
            headline: Draft
            text: "{{ page.intendedTemplate }} | {{ page.title }}"
            status: draft
            sortable: false
            sortBy: publicationDate
            templates:
              - announcement
              - event
              - call
              - article
      - width: 1/2
        sections:
          public:
            type: pages
            headline: Public
            status: listed
            sortBy: sortedUpdates
            text: "{{ page.intendedTemplate }} | {{ page.title }}

And the custom method at
site/plugins/page-methods/index.php

<?php

Kirby::plugin('my/page-methods', [
		'pageMethods' => [
				'sortedUpdates' => function () {
					$sortedUpdates = new Pages();
						if($this->children()->listed()->count() > 0) {
							$twoMonthsAgo = new DateTime();
							$twoMonthsAgo->modify('-2 month');
							$sortedUpdates = $this->children()->listed()->filterBy('pinned', 'true')->sortBy('publicationDate', 'desc');
							foreach($this->children()->listed()->filterBy('pinned', 'false')->sortBy('publicationDate', 'desc') as $c) {
									$dateIt = new DateTime($c->publicationDate());
									if($dateIt > $twoMonthsAgo ) {
										$sortedUpdates->append($c);
									}
							}		
						}
					return $sortedUpdates;
				}
		]
]);

This is a screenshot of the result at frontend and backend:

Oh, alright, I didn’t look close enough. What you use here as a page method is not really a page method but more a pages method, so you can’t really sort by that. Your custom page method or model would have to return a value for each individual page that you can sort by.

Such a page model could look like this:

public function indexValue() {
  $collection = $this->sortedUpdates();
    return $collection->indexOf($this);
}

Then in blueprint:

sortBy: indexValue

Oh I see,
in your example $this is each page that I am sorting, right?

Perhaps I could use my method on $this siblings, then find the index of $this in the produced collection?

<?php

Kirby::plugin('my/page-methods', [
		'pageMethods' => [
				'sortedUpdates' => function () {
					$sortedUpdates = new Pages();
						if($this->siblings()->listed()->count() > 0) {
							$twoMonthsAgo = new DateTime();
							$twoMonthsAgo->modify('-2 month');
							$sortedUpdates = $this->siblings()->listed()->filterBy('pinned', 'true')->sortBy('publicationDate', 'desc');
							foreach($this->siblings()->listed()->filterBy('pinned', 'false')->sortBy('publicationDate', 'desc') as $c) {
									$dateIt = new DateTime($c->publicationDate());
									if($dateIt > $twoMonthsAgo ) {
										$sortedUpdates->append($c);
									}
							}		
						}
					return $sortedUpdates->indexOf($this);
				}
		]
]);

Seems quite overkill to do all that for each page, tho… It would make more sense to have the $sortedUpdates collection created once when the sorting starts, and use it in each instance of the loop to find $this indexOf.

I can’t create it beforehand because the code uses DateTime to get the actual date and time to calculate $twoMonthsAgo.

This is probably not possible, I assume?

Thanks

My example was meant as a page model, assuming your pages in the collection are using the same template.

class SomePage extends Page {

    public function indexValue() {
      $collection = $this->sortedUpdates();
        return $collection->indexOf($this);
    }
}

I can confirm using siblings() and indexOf() in the code I added to my last response does sort as expected.

So I understand your page model would extend the page object with a custom method only for the SomePage template.

I also see this allows me to not write the sortedUpdates() code again, and I understand I can also use it as sortBy: indexValue

But, this does process the same code, right? it processes sortedUpdates() once per loop, once per $this to be sorted.

And also, then I should call sortedUpdates() on the parent of $this right?

class SomePage extends Page {

    public function indexValue() {
      $collection = $this->parent()->sortedUpdates();
        return $collection->indexOf($this);
    }
}

Thanks

I’d consider using a custom collection that you cache and update the cache when something changes. That way while you still have to use the collection to get the index of the page, it doesn’t have to be recreated each time which might slow down if you have a lot of pages.

The problem, as I explain above is that I

I can’t create [the collection] beforehand because the code uses DateTime to get the actual date and time to calculate $twoMonthsAgo.

The actual date and time at the moment of sorting, that is. Which is why I asked for a way to create the collection when sorting starts, and then use the collection in each loop for each $this indexOf. But that does not seem to be something possible.

I did not know that I could use custom methods with sortby in blueprints. Is this explained explicitly in the reference or guide ? Could you please direct me there so I can summarize a full answer?

thank you

No, I don’t think so, but the query languages works basically in the same way as the API. Admittedly, blueprint queries are currently not as powerful as the API (will be better in 3.2), but the main functionality is the same.

1 Like