Jump to content

Best practice for 'pseudo' category pages


a-ok
 Share

Recommended Posts

I've been creating categories and tags via Pages and PageFields for a while now but the thing that has always bugged me is, if for example I have a section 'News' (/news/) and a page parent/child for news items 'News (categories)' (/news-categories/) then if you filter by a category then the URL slug is /news-categories/architecture/, for example... whereas it would be nice if the URL slug was /news/categories/architecture or similar. I know this creates a few anomalies because the categories aren't actually a child page of /news/ but I think it's a lot cleaner and makes more sense visually.

I've been trying to think of the best way to achieve this. I could do a rewrite... or I could place the hidden page for categories within /news/ and work from there? I just wasn't sure if there was a solution or best practice that anyone on here has used before? I tend to have a few different category pages for different sections (News, Projects etc).

Thanks guys!

What I currently do:

News
---Article 1
---Article 2

News (categories) HIDDEN
---Category 1
---Category 2

Would this work?

News
---Article 1
---Article 2
---Category HIDDEN
------Category 1
------Category 2

 

Link to comment
Share on other sites

usually for areas like news, blog, events, the best practice is to check for the URL segments as mentioned by abdus. You can look at the blog module or blog profile to possibly see some functions for how to intercept the segment, and display the filtered posts.

  • Like 2
Link to comment
Share on other sites

Just adding to what abdus and Macrura have said: you use URL segments to do this, but you don't need to hook page paths or render any different page. Your category pages can use a template with only a title field and with no corresponding template file - these category pages only exist for the purpose of being selected in a Page Reference field in your news article pages.

In your 'news' template (the template of the parent page of the news articles) you check for a URL segment and if there is one you use that in a selector to match only news articles that have that category selected. You can do a kind of sanitizing of the URL segment by first checking if there is any page by that name using your category template or under your category parent - if there isn't then you throw a 404.

  • Like 3
Link to comment
Share on other sites

Thanks all. Super appreciated.

I have added 'category' as a URL segment (see attached) so in theory anything /news/category/ should work now.

However, I'm guessing I still need to include a rewrite on the category template? I had a look at the other forum posts from ryan and noticed this...

if ($input->urlSegment1 == 'contact') {
 // render the /about/contact/ page
 echo $pages->get('/about/contact/')->render();
 return;

} else if($input->urlSegment1) {
 // throw a 404
 throw new Wire404Exception();
}

But I am unsure how I can harness this?

Screen Shot 2017-05-05 at 18.12.35.png

Link to comment
Share on other sites

5 hours ago, oma said:

I have added 'category' as a URL segment (see attached) so in theory anything /news/category/ should work now.

No, doing this would only allow the exact URL segment 'category'. Instead, don't place any restrictions on the segment in the template settings and do your segment validation inside the 'news' template.

5 hours ago, oma said:

However, I'm guessing I still need to include a rewrite on the category template?

Rather than rewrite or render anything from a category page you can handle all the output within your 'news' template. Here is an example from a recent project:

$limit = 5;
$news_selector = "template=news_item, post_date<=today, limit=$limit, sort=sort";

$categories = $pages(1139)->children();
$segment_1 = $sanitizer->pageName($input->urlSegment1, true); // URL segments are already sanitized as page names but not to lowercase
if($segment_1) {
    $current_category = $categories->get("name=$segment_1");
    if($current_category) {
        // the segment is a valid news category
        $news_selector .= ", news_category=$current_category";
        $page_title = "$current_category->title news";
    } else {
        // the segment is invalid so throw 404
        throw new Wire404Exception();
    }
}

$news_items = $pages->find($news_selector);
$total_pages = ceil($news_items->getTotal() / $limit);

The effect of this is that if there is a valid URL segment then only news items from that category are listed, but if there is no URL segment then all news items are listed.

  • Like 7
Link to comment
Share on other sites

15 hours ago, Robin S said:

No, doing this would only allow the exact URL segment 'category'. Instead, don't place any restrictions on the segment in the template settings and do your segment validation inside the 'news' template.

Rather than rewrite or render anything from a category page you can handle all the output within your 'news' template. Here is an example from a recent project:


$limit = 5;
$news_selector = "template=news_item, post_date<=today, limit=$limit, sort=sort";

$categories = $pages(1139)->children();
$segment_1 = $sanitizer->pageName($input->urlSegment1, true); // URL segments are already sanitized as page names but not to lowercase
if($segment_1) {
    $current_category = $categories->get("name=$segment_1");
    if($current_category) {
        // the segment is a valid news category
        $news_selector .= ", news_category=$current_category";
        $page_title = "$current_category->title news";
    } else {
        // the segment is invalid so throw 404
        throw new Wire404Exception();
    }
}

$news_items = $pages->find($news_selector);
$total_pages = ceil($news_items->getTotal() / $limit);

The effect of this is that if there is a valid URL segment then only news items from that category are listed, but if there is no URL segment then all news items are listed.

This is special. Thanks, Robin. I had no idea this was the best approach and makes a lot of sense (categories still exist in their own page tree outside news but can exist within via the URL).

Thanks to everyone else for chiming in too!

  • Like 1
Link to comment
Share on other sites

  • 6 months later...

Sorry to bring this up again but felt it was best to keep it under the same topic.

I'm wondering if it's possible to do the same with multiple categories?

Obviously this line would need to change...

$current_category = $categories->get("name=$segment_1");

Any thoughts? Obviously the categories returns would be piped (for example, music|art) is that right? I don't mind using ids either... but just curious to know if you think it's possible.

Link to comment
Share on other sites

On 23/11/2017 at 9:52 AM, oma said:

I'm wondering if it's possible to do the same with multiple categories?

A URL segment can include any characters that are valid in a page name.

Quote

Page names by default support lowercase ASCII letters, digits, underscore, hyphen and period.

So you could separate your category names with an underscore or period perhaps, then explode on that character and match each category to pages.

But you'd be sort of fighting against the workings of URL segments so why not use a GET variable instead? Then you can separate your categories with any character you want...

http://yourdomain.com/your-page/?categories=one|two

...and support for arrays is actually built-in...

http://yourdomain.com/your-page/?categories[]=one&categories[]=two

 

  • Like 2
Link to comment
Share on other sites

I opted for this one

7 hours ago, Robin S said:

So you could separate your category names with an underscore or period perhaps, then explode on that character and match each category to pages.

here:

https://www.szepelet.com/products/product-categories_cleansers/

for example:
product-categories_body-care
vs
skin-concerns_body-care

Where product-categories and skin-concerns are parent categories while body-care and body-care are different subcategories under their respective parents. However, these categories are sort of set in stone, meaning that they rarely change – if ever – and not manageable by the site editor by design.

  • Like 2
Link to comment
Share on other sites

12 hours ago, Robin S said:

A URL segment can include any characters that are valid in a page name.

So you could separate your category names with an underscore or period perhaps, then explode on that character and match each category to pages.

But you'd be sort of fighting against the workings of URL segments so why not use a GET variable instead? Then you can separate your categories with any character you want...


http://yourdomain.com/your-page/?categories=one|two

...and support for arrays is actually built-in...


http://yourdomain.com/your-page/?categories[]=one&categories[]=two

 

Thanks, Robin. I have come up with the following on my overview template (replacing the setup for URL segments and replacing it with a GET setup)

$limit = 18;
$query = "template=where-to-go-detail|our-guides-detail, sort=sort, limit=$limit";

if ($input->get("tags")) {

	var_dump($input->get("tags"));

	$currentTags = $input->get("tags");
	$currentTags = $sanitizer->text($currentTags);
	
	$query .= ", tags={$currentTags}";
	$articles = $pages->find($query);

} else {

	$articles = $pages->find($query);

}

What do you think? Looks good? I would then use isset($currentTags) to check if a tag has been queried.

Link to comment
Share on other sites

What I can't work out is why this wouldn't work?

$limit = 18;
$articles = $pages->find("template=where-to-go-detail|our-guides-detail, sort=sort, limit=$limit");

if ($input->get("tags")) { // If GET ?tags= exists...

	$currentTags = $input->get("tags");
	$currentTags = $sanitizer->text($currentTags);

	$articles->filter("tags={$currentTags}"); // Append original query with the tags selector

	if (count($articles)) {
		continue;
	} else {
		throw new Wire404Exception();
	}

}

 

Link to comment
Share on other sites

I believe when using 'filter->()' it's using IDs? So I had to do...

$limit = 18;
$articles = $pages->find("template=where-to-go-detail|our-guides-detail, sort=sort, limit=$limit");

if ($input->get("tags")) { // If GET ?tags= exists...

    $currentTags = $input->get("tags");
    $currentTags = $sanitizer->text($currentTags);

    $articles->filter("tags.name=$currentTags"); // Append original query with the tags selector

    if (!count($articles)) throw new Wire404Exception();

}

 

Link to comment
Share on other sites

9 hours ago, oma said:

I have come up with the following on my overview template (replacing the setup for URL segments and replacing it with a GET setup)


$limit = 18;
$query = "template=where-to-go-detail|our-guides-detail, sort=sort, limit=$limit";

if ($input->get("tags")) {

	var_dump($input->get("tags"));

	$currentTags = $input->get("tags");
	$currentTags = $sanitizer->text($currentTags);
	
	$query .= ", tags={$currentTags}";
	$articles = $pages->find($query);

} else {

	$articles = $pages->find($query);

}

 

This is a better way to go than your later examples. It's more efficient to use the GET tags as part of the $pages->find() selector than to find a larger number of pages and then filter it.

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.
×
×
  • Create New...