Jump to content

Selector question


deltavik
 Share

Recommended Posts

Hey guys, 

   I thought I had selectors under control until I ran into this situation. 

This is my structure of four templates: 

Articles
- Article

Authors
- Author

Article contains a pagefield for author. If I wanted to get all articles written by an author, I can run a selector such as "template=article, author=$authorName".  So far so good. 

Here is where I am stuck: I want to display a list of top 10 authors with the most number of articles written.  In other words, I want to write a selector that

- finds items with template=author 

- sorts descending by the count of articles written by author <-- I am unable to formulate this part

- limits items to 10

One way to solve this is: Get a count of articles by all authors in a php array. Then sort the resultset by article count and get author names for the top ten in the array. This method can be heavy on both database and php, depending on the number of authors and articles in the system.

Is there a selector-only trick to solve this problem?

thanks

 

Link to comment
Share on other sites

As far as I remember this is not yet possible as there is no "reverse" selector. This is discussed somewhere in the forums..

Think your way is the easiest. You could cache it using wirecache for example..
https://processwire.com/talk/topic/10548-my-way-of-using-wirecache-and-some-hints-to-make-life-easier-with-it/

Another possibility would be to store the needed information (number of articles) in a field e.g. "article_count", so every time an author writes an article you increment or decrement if an article gets deleted..
if authors use pw backend you would hook after Page::save (or maybe Page::saveReady) and update the value accordingly, like this you could hook into trashing or unpublishing etc of pages.
but maybe you got some front end form where authors post their content then you could just add something like $user->setAndSave('article_count', $user->article_count+1) (there might be a shorter syntax but I can't test right now..) after your existing article save logic..

 

Link to comment
Share on other sites

thanks @Can  for the wirecache example. That will come handy.

I am thinking of running the sql query directly to get results. This seems quite efficient. And, I will also cache the results of this. 

This is my query: 

SELECT count(pages_id), data as author FROM field_authors 
GROUP BY data
ORDER BY count(pages_id) DESC
LIMIT 10;

 

  • Like 1
Link to comment
Share on other sites

Getting the authors with the most articles would be simple if the article pages were added to author pages rather than the other way around, but of course that's not so good for workflow. But there have been some posts on the forum that suggest linking two Page fields, where changing one updates the other.

These may also be relevant:

 

  • Like 2
Link to comment
Share on other sites

I would do something like you mentioned:

   $articleCountPerAuthor = array();
   foreach($pages->find("template=author") as $author){
		$articleCount = $pages->count("template=article, author=$author");
   		$articleCountPerAuthor[$author->title] = $articleCount;
   }
   arsort($articleCountPerAuthor);
 
 	// result of $articleCountPerAuthor
	/*
 	array(
		"Actor XXX" => 500, 
		"Actor XXX" => 400,
		"Actor XXX" => 200,
	)*/

$pages->count() - little overhead, since it doesn't load the pages like $pages->find() does

arsort() - Sort an array in reverse by array value

Since Processwire is extremly performant and efficient in handling pages you shouldn't have Problems.

Edited by Nukro
duplicate keys possible, changed key/value pair, replaced krsort() with arsort()
  • Like 4
Link to comment
Share on other sites

5 hours ago, Nukro said:

$articleCountPerAuthor[$articleCount] = $author->title;

What if we have the same number of articles? It will overwrite the previous one, won't it?

Edited by szabesz
spelling
  • Like 1
Link to comment
Share on other sites

17 minutes ago, szabesz said:

What if we have the same number of articles? It will overwrite the previous one, won't it?

Oh, missed that ^^.

I have updated the post. Thanks @szabesz. It's still possible to have duplicate keys(authors which has the same name?) but probably is less likely. You could add a random prefix or something which you can remove at output with str_replace() or exlode() or something...

  • Like 1
Link to comment
Share on other sites

2 hours ago, Nukro said:

It's still possible to have duplicate keys(authors which has the same name?) but probably is less likely. You could add a random prefix or something which you can remove at output with str_replace() or exlode() or something...

Every thing is a page! Om! Every page has a ID! Om!

$articleCountPerAuthor[$author->id] = $articleCount;

Should be save! :lol:

  • Like 4
Link to comment
Share on other sites

Let's suppose we are absolutely sure that $author->title is unique. In that case your example is a valid one if we do not need the ID in a particular use case (e.g. we just want to use the result once for echoing it out and throw it away afterwards). However, if we have the slightest doubt that this is not the case, then  @horst is right I think. The best practice is to always stick to IDs when we can. So, thank you horst for pointing this out!

Link to comment
Share on other sites

3 hours ago, horst said:

Every thing is a page! Om! Every page has a ID! Om!


$articleCountPerAuthor[$author->id] = $articleCount;

Should be save!

Another option is to set the article count as a property of the author Page object.

$authors = $pages->find("template=author");
foreach($authors as $author){
    $author->articleCount = $pages->count("template=article, author=$author");
}
$authors->sort("articleCount");

 

  • Like 2
Link to comment
Share on other sites

18 hours ago, Robin S said:

Getting the authors with the most articles would be simple if the article pages were added to author pages rather than the other way around, but of course that's not so good for workflow. But there have been some posts on the forum that suggest linking two Page fields, where changing one updates the other.

These may also be relevant:

 

helpful threads. Seems like many people have run into this situation. I thought I missed something in the tutorials. 

 

Link to comment
Share on other sites

12 hours ago, Nukro said:

I would do something like you mentioned:


   $articleCountPerAuthor = array();
   foreach($pages->find("template=author") as $author){
		$articleCount = $pages->count("template=article, author=$author");
   		$articleCountPerAuthor[$author->title] = $articleCount;
   }
   arsort($articleCountPerAuthor);
 
 	// result of $articleCountPerAuthor
	/*
 	array(
		"Actor XXX" => 500, 
		"Actor XXX" => 400,
		"Actor XXX" => 200,
	)*/

$pages->count() - little overhead, since it doesn't load the pages like $pages->find() does

arsort() - Sort an array in reverse by array value

Since Processwire is extremly performant and efficient in handling pages you shouldn't have Problems.

Yes, something like this was the pseudo code in my head. For this case, I went with sql query straight up. Given the high number of articles and authors, I thought it will be efficient if I just do a private, one-on-one chat with the database. 

The selector-trick will be useful for smaller datasets.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By Robin S
      Lister Selector
      A Process module that uses Lister/ListerPro, but with a selector string input instead of the normal InputfieldSelector filters.
      Features
      For power users, typing a selector string is often faster and more intuitive than fiddling with InputfieldSelector. It also lets you copy/paste selector strings that you might be using somewhere else in your code.
      Allows the Lister rows to be sorted by multiple fields (not possible in Lister/ListerPro)
      Allows the use of OR-groups (not possible in Lister/ListerPro)
      If ListerPro is installed you can run ListerPro actions on the listed pages - the available actions are defined in the module config.
      Bookmarks can be configured in the module config and accessed via the flyout menu for the module page. For your convenience you can copy/paste a bookmark string from the note at the bottom of the Lister Selector results.
      Usage
      Type your selector string on the Selector tab. The selector is applied when the "Selector string" field is blurred, so hit Tab when you have finished typing your selector.
      Unlike Lister/ListerPro, you can't sort results by clicking the column headings. Control the sort within the selector string instead.
      Superusers can jump to the module config (e.g. to create a bookmark) by clicking the cog icon at the top right of the module interface.
      The module is mostly intended for use by superusers, because in most cases site editors won't understand the ProcessWire selector string syntax. If you want another role to be able to access Lister Selector then give the role the "lister-selector" permission. Only superusers can define bookmarks because in ProcessWire module config screens are only accessible to superusers.
      Screenshots
      Process page

      Module config (when ListerPro is installed)

      Advanced
      If for any reason you want to create dynamic bookmark links to Lister Selector for a given selector you can do that like this:
      /** @var $pls ProcessListerSelector */ $pls = $modules->get('ProcessListerSelector'); // Define selector $selector = "template=foo, title%=bar"; // Define columns (optional) $columns = 'title,modified'; $pls_link = $pls->getProcessPage()->url . '?bm=' . $pls->urlSafeBase64Encode($selector . ':' . $columns); echo "<a href='$pls_link'>My link</a>";  
      https://github.com/Toutouwai/ProcessListerSelector
      https://modules.processwire.com/modules/process-lister-selector/
    • By Peter Knight
      Hey I'm building my first new site in well over a year and am a little rusty on selectors but particularly retrieving sub-fields of selected pages.
      I am trying to output the meta data of a blog post as follows.
      [Person Name] is just a field with a Page Reference and simple enough.
      [Job Title] is the sub-field within the page that was referenced above.
      I actually have it working with the following:
      Posted by: <?php if($page->insight_author) { echo $page->insight_author("<a href='{url}'>{title}</a>");} ?> , <?php $roles = $page->insight_author; foreach ($roles as $role) { echo "{$role->staff_role}";} ?> but was wondering how to do this with selector sub-selectors instead. My current code is probably quite 'old school'?
      Thanks
       
    • By theoretic
      Hi there! And thanks for Processwire!
      I have an interesting task which i cannot fulfill as i want. Maybe someone could help me please?
      Let's imagine a simple page structure of this kind:
      Category 1
      + Item 1.1
      + Item 1.2
      Category 2
      + Item 2.1
      + Item 2.2
      My task is to attach some items to more than one category, at least to show some items on different frontend category pages. With PW, it's a piece of cake. I've just created a field called Items (of type Page Reference) and attrached it to Category template. Since i have lots of items inside each category i preferred to use Page Autocomplete input for my Items field. The pages available for autocomplete are restricted by a very simple selector:
      template=item
      It works like a charm. But later i decided to make this autocomplete even smarter and to exclude current category children items from it. I tried to update my selector this way...
      template=item,parent!=(page)
      ...and oops, this broke my selector. My autocomplete founds nothing. Sorry, i had to replace the square braces by () because of this forum limitations, i swear i'm using square brackets in real-life selector!
      What am i doing wrong? And is there any way to include current page info in autocomplete-related selectors? Thanks in advance!
       
    • By ottogal
      Hello all,
      using PW 3.0.148 with the regular site profile for a blog, I got an an empty pagination output when I had a Toggle field in the selector.
      The Toggle Fieldtype was introduced with https://processwire.com/blog/posts/pw-3.0.139/ .
      The selector resulting in empty pagination:
      $posts = $pages->find("parent=blog, sort=-date, limit=10, toggle_field=0"); It worked well, when I replaced the Toggle field with a Checkbox field:
      $posts = $pages->find("parent=blog, sort=-date, limit=10, checkbox_field=0"); So the prerequisites for the pagination to work are given.
      The settings for the Toggle field were:
      Formatted value: Integer Label Type: Yes/No Input Type: Toggle buttons Default selected option: No Thanks for any hints!
    • By snck
      Hi there,
      I have a problem constructing a selector that finds all pages that refer to pages with a specific template.
      I have pages using an event template and I want to show events based on a specific context. In this example I want to filter the results and only show event pages that relate to a specific template (exhibitions) in their page field related_pages.
      What I tried:
      $events = $pages->find("template=event, related_pages.template.name=exhibition"); Unfortunately it does not work (0 results).
      Same with this:
      $events = $pages->find("template=event, related_pages=[template.name=exhibition]"); At the moment I am helping myself with the following lines, but I have a strong feeling that there is a more efficient solution:
      $events = $pages->find("template=event"); foreach($events as $event){ if(!count($event->related_pages->find("template=exhibition"))){ $events->remove($event); } }  
      I really hope that one of you can help me out.
      Thanks in advance!
      Flo
×
×
  • Create New...