Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show (grand)parent-page title #627

Closed
ctuxboy opened this issue May 2, 2022 · 18 comments
Closed

Show (grand)parent-page title #627

ctuxboy opened this issue May 2, 2022 · 18 comments

Comments

@ctuxboy
Copy link

ctuxboy commented May 2, 2022

Hello,

I have a dir tree:
../province/city/location
Is it possible show the province-page title in the location-page in my theme?

Trying this but doesn't work
{{ parent.parent.page.title }}

@mayamcdougall
Copy link
Collaborator

Hi there. 👋🏻

There's no parent property on a Pico page to work with. I've got two potential solutions for you though.

The simplest way would probably be to use the pages() function to return the grandparent of the current page, then get the title from there:

{{ pages(current_page.id,offset=-2)|first.title }}

We also need the |first filter in this case because even though there's only one result, it's still in an Array, since pages() is normally used to access more than one item.

I'm not 100% solid on the usage of pages(), and specifically the offset parameter here, so this was actually my second solution. @PhrozenByte, if you could fact check me here that this is the correct usage of pages/offset, that'd be great.

My first solution was a lot more hacky. I'm more confident in saying "this will definitely do what you asked" about this one... but I'm also confident that it's a lot more hacky, lol. 😂

{{ pages[current_page.id|split("/",-2)|join("/")].title ?: pages[current_page.id|split("/",-2)|join("/") ~ "/index" ].title }}

Here we're taking the current_page.id, splitting it at every /, chopping off the last two (-2), then rejoining them and using that as the new page id to fetch from pages[ ] (to then get the title of). However, since the grandparent page could be at two different locations, I'm first check to see if it gets anything from the page at province.md (in your example) before falling back to province/index.md. That little ?: in the middle is a ternary operator that is basically condensing an if statement in to the middle of the logic here. 😅

(Obviously, if you knew where you were keeping your page, either province.md or province/index.md, you could choose to omit one side of that statement or the other.)

Anyway, try the pages() version first, and if that doesn't work you can try the hacky version.

Let me know if this helps and don't be afraid to ask any other questions you might have. 😁

@ctuxboy
Copy link
Author

ctuxboy commented May 3, 2022

@mayamcdougall ,
Wow thanks for your great and fast explanation, appreciate it a lot :)
I will tell you your first solution works great!
{{ pages(current_page.id,offset=-2)|first.title }}

Screenshot 2022-05-03 11 11 52

I'm the owner of dogvalley.be, build on top of another flatfile cms (automad), but i will rebuild dogvalley, and will test if picocms is a good 'candidate' for my requirements. I love the pico-simplicity, the extenable-possibilities (plugins), and the easy way integrate my own theme. Okay, but i asking some 'technical-things' in another issue soon.

PS: sorry for my poor english.

Christophe

@ctuxboy ctuxboy closed this as completed May 3, 2022
@ctuxboy
Copy link
Author

ctuxboy commented May 3, 2022

UPDATE: adding also the url, so now i have a link to the grandparent-page :)

<a href="{{ pages(current_page.id,offset=-2)|first.url }}">{{ pages(current_page.id,offset=-2)|first.title }}</a>

@mayamcdougall
Copy link
Collaborator

Glad it's working for you. 😁

Just let me know if you have any other questions. 😉

Btw, if you're using that in multiple places, you can save it as a variable to make things either too.

{% set grandparent = pages(current_page.id,offset=-2)|first %}
<a href="{{ grandparent.url }}">{{ grandparent.title }}</a>

@PhrozenByte, even though this issue is "solved", could you still let me know about that offset parameter? The Docs aren't worded great around it. "There’s only one common use case: Accessing a page’s parent page." makes it sound like it would only ever return the one page, but if that's the main purpose of it, idk why you also refer to it as "pretty advanced stuff". 😉 😂

@ctuxboy
Copy link
Author

ctuxboy commented May 3, 2022

Glad it's working for you. 😁

Just let me know if you have any other questions. 😉

Btw, if you're using that in multiple places, you can save it as a variable to make things either too.

{% set grandparent = pages(current_page.id,offset=-2)|first %}
<a href="{{ grandparent.url }}">{{ grandparent.title }}</a>

@PhrozenByte, even though this issue is "solved", could you still let me know about that offset parameter? The Docs aren't worded great around it. "There’s only one common use case: Accessing a page’s parent page." makes it sound like it would only ever return the one page, but if that's the main purpose of it, idk why you also refer to it as "pretty advanced stuff". 😉 😂

Thanks for the extra tip! :-)

@PhrozenByte
Copy link
Collaborator

@PhrozenByte, even though this issue is "solved", could you still let me know about that offset parameter? The Docs aren't worded great around it. "There’s only one common use case: Accessing a page’s parent page." makes it sound like it would only ever return the one page, but if that's the main purpose of it, idk why you also refer to it as "pretty advanced stuff". 😉 😂

@mayamcdougall, the offset parameter basically tells the pages() function not to use the given page as starting point when traversing the page tree, but its descendant (positive offset) or ancestor (negative offset) pages. It defaults to offset=1, meaning that it will start with the given page's child pages. offset=2 causes the pages() function to start with the given page's grandchild pages. A negative offset means that traversal starts with the given page's respective parent (offset=-1), or grandparent (offset=-2) page. Since the pages() function returns only one level of pages by default, it will then return just these pages.

By the way: The "actual" description of what the parameters mean can be found in the class docs. It's a rather technical description. I very much like the pages() function because it is very elegant from a technical perspective, but quite hard to understand if you don't know the code. My intention with the in-depth docs was to describe it in a more "non-techie" way... This wasn't that easy for me and I'm quite sure that I didn't do very well. So I'd be very happy about a rewrite of this docs page by someone who doesn't know the code 😉

In the future (whenever...) we might make tree traversal way easier by introducing page object keys to specifically access the respective parent or child pages.

@mayamcdougall
Copy link
Collaborator

I think the most confusing thing about pages() is that offset, depth, and depthOffset are so very similar in usage and function that it's hard to predict what pages will be returned without just running the function and seeing if it's the result you expected.

I think the class docs make my point well right here:

Passing $depth = 0, $depthOffset = -2 and $offset = 2 is the same
* as passing $depth = 2, $depthOffset = 0 and $offset = 0. Both will
* return the zeroth, first and second generation of pages. For Pico's main
* index page this would be index.md (0th gen), sub/index.md (1st gen),
* sub/page.md (2nd gen) and page.md (1st gen). If you want to return
* 2nd gen pages only, pass $offset = 2 only (with implicit $depth = 0
* and $depthOffset = 0 it's the same as $depth = 2, $depthOffset = 2
* and $offset = 0).

I wonder if it would be better to actually mention this math somewhere in the in-depth doc, because actually knowing what each number means (instead of explaining with only words and examples), could make it more predictable to use (for some people anyway... numbers make my head spin. 😂 )

But anyway, it sounds like my intuition was right with this, and |first was the right choice for making sure we only fetch the (grand)parent page.

I just wanted a little more confidence that my suggestion above was going to continue to behave as expected no matter the environment. I didn't have a lot of pages in my test (using the sample-docs, plus an added depth level), and I didn't want to set @ctuxboy up for failure if this wasn't the correct usage of offset.

I was going to write out a list of definitions here for each parameter, as I understood them, to make sure I had it down...

Then I realized you already have the perfect definition underneath the over-explained paragraphs!

     * @param string   $start       name of the node to start from
     * @param int|null $depth       return pages until the given maximum depth;
     *     pass NULL to return all descendant pages; defaults to 0
     * @param int      $depthOffset start returning pages from the given
     *     minimum depth; defaults to 0
     * @param int      $offset      ascend (positive) or descend (negative) the
     *     given number of branches before returning pages; defaults to 1

👆🏻 THIS should be on the in-depth page. This is what we should lead with! 😂

The paragraphs can come afterward (not in the PHPDocs, I get why it comes first there, just on the in-depth page).

I know you probably think this is the more technical explanation, but these simple one-line parameter descriptions are actually perfect. 😍

The pages() function has always felt like a bit of a "black box" to me, since I've really only ever needed to use depthOffset=-1 for navigation.

Do I need depth? depthOffset? offset? Idk, keep trying until one of them works. 🤷🏻‍♀️

It's one of those things that I've meant to figure out how it works... when I have time... which never happens... etc.

But those couple lines of parameter definitions? They are WAY more understandable than the thick metaphors you wrote for the in-depth page!

I really mean no offense there... (😅) your metaphors on that page are good metaphors, but I think they drag out the definition a bit much. The comparisons themselves are good, but they might actually be overcomplicating things. It's asking the reader to juggle both the metaphor and what it means for the underlying function in their head.

As I've learned with the bits of the docs I've rewritten, simpler is always better. And that means fewer words too, not just simpler language. Too much padding can be just as much of a problem as too much information!

Alright, enough of this rant. Thanks for unintentionally pointing me where I needed to go with the class docs. 😂

If I think about this anymore right now, I'm going to end up rewriting the existing in-depth page today, lol.

And while that'd be a good thing, I've really got other priorities to address (plus, it would be better served for the proper rewrite). 😅

Thanks again. ❤️

@PhrozenByte
Copy link
Collaborator

But anyway, it sounds like my intuition was right with this, and |first was the right choice for making sure we only fetch the (grand)parent page.

That's more due to the fact that pages() is designed to return a list of pages, even though the parameters might effectively dictate to match only a single page. It's then going to be a "list" of pages with just one entry.

I just wanted a little more confidence that my suggestion above was going to continue to behave as expected no matter the environment.

It will 👍

I really mean no offense there... (:sweat_smile:)

None taken. I already said that I don't think that it ended up well 😝

If I think about this anymore right now, I'm going to end up rewriting the existing in-depth page today, lol.

Looking forward to it! 👍 ❤️

And while that'd be a good thing, I've really got other priorities to address (plus, it would be better served for the proper rewrite). sweat_smile

😆 😉 👍

@mayamcdougall
Copy link
Collaborator

That's more due to the fact that pages() is designed to return a list of pages, even though the parameters might effectively dictate to match only a single page. It's then going to be a "list" of pages with just one entry.

I know that much, lol. I said as so when I suggested it. 👆🏻😉

I just didn't know if I ever had to worry about offset returning more than one page (eg, the parent and some children), in which case I hoped the "first" one would still be the parent/grandparent of the selection. So I chose the |first filter as the "most appropriate" way to always get the desired page from the array, just in case there was ever a situation where it returned children too.

I wrote that part of my comment earlier though, and looking at the descriptions again, it sounds like "offset" really will only ever return one page unless a depth is specified, since depth defaults to zero.

Looking forward to it! 👍 ❤️

I said I wasn't going to do that right now. 😂

Though I still have to emphasize how much those short descriptions help with understanding pages(). Consider it officially de-mystified for me. 😉

@PhrozenByte
Copy link
Collaborator

PhrozenByte commented May 4, 2022

Another explanation just came to my mind I wanna share. You might wanna use it for your rewrite - or not, no hard feelings if you don't, I leave this totally up to you 😉


The following pages are given:

content/
├── shop/
│   ├── flowers/
│   │   ├── daisies.md
│   │   ├── red-roses.md
│   │   └── tiger-lilies.md
│   ├── index.md
│   └── shipping.md
├── index.md
└── contact.md

Think about pages in generations. Let's represent every generation by a single character: A is for index.md, B is for all top-level pages (i.e. contact.md and shop/index.md), C is for all second-level pages (i.e. shop/shipping.md), D is for all third-level-pages (i.e. shop/flowers/daisies.md, shop/flowers/red-roses.md and shop/flowers/tiger-lilies.md). Let's write this as ABCD.

Now think about the pages() function as some sort of "selector", selecting page generations. For example, to create the main menu, you want all pages of the A and B generations (i.e. index.md, contact.md and shop/index.md). Basically you want ABCD, bold meaning that the pages() function shall return all pages in these generations (i.e. the "selected" pages).

The pages() function now returns a given page's children by default, i.e. for shop/index.md (B) it will return shop/shipping.md (ABCD). For index.md (i.e. A) it will return ABCD (i.e. contact.md and shop/index.md), for any page in C it will return ABCD. The depth parameter controls the (right) end of the selector. By passing depth=1 you expand the selector by one generation (depth=2 by two generations, etc.), i.e. depth=1 returns ABCD for any page in B (shop/shipping.md, shop/flowers/daisies.md, shop/flowers/red-roses.md and shop/flowers/tiger-lilies.md for shop/index.md, but none for contact.md!). The depthOffset parameter controls the start (left "end") of the selector. Since our selector must at least be one page generation wide, we can't increase depthOffset without also increasing depth. So, if you pass depth=1 depthOffset=1, depth=1 first expands the selector to two generations, but depthOffset=1 immediately skips the oldest generation in the selector. Thus the function returns ABCD for any page in B.

Let's talk about offset. offset=1 is the default and actually the reason why the pages() function returns a given page's child pages by default. offset moves the whole selector around, both the start and end of it. The default offset=1 moves the selector to ABCD for any page in B, with offset=0 it would rather return ABCD - what makes no sense since it's the given page. By passing offset=2 you move the whole selector to the right, so that ABCD is returned for any page in B. Did you remember that you saw this before? Right, it's the same as depth=1 depthOffset=1. It's not just similar, it is the same! offset works as an shortcut to increase depth and depthOffset with just one parameter.

However, offset is a bit more powerful than depth and depthOffset: It can not just move the selector to the right, but also to the left. If you pass offset=-1 you move the whole selector one generation to the left (offset=-2 by two generations, etc.). This won't work for pages in A, because there's no older generation, but for pages in B. offset=-1 returns ABCD for any page in B. If you want the same for pages in C, use offset=-2. Naturally you can also combine offset and depth: If you want ABCD for pages in C, use offset=-2 depth=1.

Let's get back to our original example: The main menu. How do we create the main menu? We use index.md (i.e. A) as starting point. pages() will return ABCD by default. To also include index.md we pass offset=0. We now get ABCD. To also include the B's, we must also increase depth: offset=0 depth=1 returns the requested ABCD. You remember that offset is a shortcut to both depth and depthOffset? You can achieve exactly the same by passing just depthOffset=-1.

@mayamcdougall
Copy link
Collaborator

I think that's a much better example than what's on in-depth right now.

I'd still start with the definitions, but together with this explanation, I think it would be in pretty good shape.

I like that this explanation focuses more on explaining how and why pages() works the way it does rather than trying to explain reason why you might use it a particular way.

I think that's another reason the current version is hard to read. It tries to force you to imagine a bunch of situations where you'd want specific examples, rather than just explaining how pages() works upfront, and letting the you compare it to your own use case.

Maybe I will just throw together a quick update for the in-depth page then... 🤔

Also, I thought of this visualization earlier, which could work (though I might make it a graphic instead of just some ASCII art):

                         start
                           |
|--------------------------|--------------------------|
<-- depthOffset      <-- offset -->           depth -->

@mayamcdougall
Copy link
Collaborator

And sorry to @ctuxboy if you're getting notifications for all these tangentially-related discussion comments. 😅

As I said before, don't be afraid to ask any other questions if you have them, and/or don't be afraid to open a New Issue with them if you'd like to get away from this discussion and ask something. 😂

@PhrozenByte
Copy link
Collaborator

PhrozenByte commented May 4, 2022

I'll think about whether we can add offset and length parameters as an alternative in the future. Defaulting to offset=1 length=1 we would return ABCD for pages in B. By increasing length we get more generations, decreasing is not possible (throws an error). length=null returns all following generations. By increasing offset we move to the right, by decreasing offset we move to the left.

  • pages(…) remains pages(…) both returning ABCD for B
  • pages(…, depth=0) is the default pages(…) both returning ABCD for B
  • pages(…, depth=1) gets pages(…, length=2) both returning ABCD for B
  • pages(…, depth=2) gets pages(…, length=3) both returning ABCD (plus the non-existing E) for B
  • pages(…, depth=1, depthOffset=1) still is the same as pages(…, offset=2) both returning ABCD for B
  • pages(…, depth=2, depthOffset=1) gets pages(…, offset=2, length=2) both returning ABCD (plus the non-existing E) for B
  • pages(…, depth=2, depthOffset=2) gets pages(…, offset=3) both returning nothing for B (because there's no E)
  • pages(…, offset=0) remains pages(…, offset=0) both returning ABCD for B
  • pages(…, offset=1) is the default pages(…) both returning ABCD for B
  • pages(…, offset=2) remains pages(…, offset=2) both returning ABCD for B
  • pages(…, offset=-1) remains pages(…, offset=-1) both returning ABCD for B
  • pages(…, offset=-2) remains pages(…, offset=-2) both returning ABCD for B (because there's nothing below A)
  • pages(…, offset=0, depth=1) gets pages(…, offset=0, length=2) both returning ABCD for B
  • pages(…, offset=0, depth=2) gets pages(…, offset=0, length=3) both returning ABCD for B

Thinking about it we should probably make this the default... We will still support depth and depthOffset though. offset works as before anyway. One can't mix depth/depthOffset and length (it will throw an error). Not for Pico 3.0 though, I just added it to the backlog.

@ctuxboy
Copy link
Author

ctuxboy commented May 4, 2022

@mayamcdougall , @PhrozenByte ,
No problem ;-) Love the lot of information.
Feels good to see the Pico-team and community being actively used and maintained this great project.
These issues are very helpful to me, and I read them with a lot of interest.
At the moment i tested and experimented with PicoCMS, with success.
In the past works with other cms-systems, flatfile (Grav, Automad, Get Simple cms,...), also with databases (WordPress, Drupal, Concrete5,...), but my requirements is simple and flexibility. I'm not a hardcore (php) programmer, but i like it made some simple snippets and building my own theme/plugins in an easy way, so i have a good feeling with Pico CMS. Only i want test/experiment if Pico can handle 500+ pages without problems.
PS: GravCMS works great, but i think is more difficult developing snippets and other stuff opposite Pico, i feel it is easier developing for Pico :-)

@mayamcdougall
Copy link
Collaborator

I'll think about whether we can add offset and length parameters as an alternative in the future.

I'm not actually sure that it needs to change. The parameter names fit well enough... they just need better explanations.

Between the short descriptions and the little ascii diagram, I think it's actually really approachable now. 😳


@ctuxboy

but my requirements is simple and flexibility

That's why we like Pico too. 😉

I'm not a PHP programmer either, and you'd be amazed the stuff you can do in Pico with just some clever Twig code.

In addition to the Default Theme, you can check out my recent Freelancer and Stellar theme ports, for some interesting Twig examples.

Only i want test/experiment if Pico can handle 500+ pages without problems.

Yes, one (minor) shortcoming Pico has is scalability, due to the fact that everything is done on-the-fly, without caching. You might have seen this mentioned recently in #624 if you've been looking through the issues. Fortunately, we're just talking about a few extra milliseconds in most cases.

And of course the pages() function we've been discussing is one of the ways Pico can be better optimized for large page counts as well. Using pages() instead of accessing the pages variable directly lets you only operate on the pages you need, preventing you from having to iterate through them all.

You might also notice that a lot of the older Pico themes don't use the pages() function, as they were written before it existed. If you end up using an older theme, or borrowing any code from them, just be sure to keep that in mind.

Pico might take a little more work to get going than other CMSs, but the extra control and flexibility you get is well worth it. You can do just about anything with Pico if you're willing to put in the time. 😉

@ctuxboy
Copy link
Author

ctuxboy commented May 5, 2022

Okay nice , i have the idea choosing the perfect cms :-))
I read #624 , very interesting, learning when split the html-pages in code blocks, it can boost up Picocms.
It the past i was a Wordpress specialist, but have an love/hate relation with WP, it is good for clients, but for my own projects i like the freedom and flexibility what PicoCMS have, so you have 100% control about your own snippets/plugins/themes. In WP it is a lot of working building a custom theme, in Pico i like the easy way embed a theme, i love that :-)
GRAV is also awesome, but have a lot of features, okay thats nice, but realise that i'm not use all the options in Grav, and make it also more difficult integrate your own stuff.
Last but not least Automad is also awesome, but it has also he's issues, it is a one-men-project, so what if this person stops the project? It missed user roles, and some other things.
It is hard the find a cms that fits all your needs. Okay, a few years ago tested PicoCMS, but it wasn't the right cms for my client, without technical skills, but for my own projects, it is really awesome, okay some challenges, but not to hard for experimenting and gives me full control, and hope see more Pico versions and more plugins in the future :-)

Thanks for sharing your themes, can help a lot find how is the best way implement some code (snippets).

@mayamcdougall
Copy link
Collaborator

very interesting, learning when split the html-pages in code blocks, it can boost up Picocms.

An important thing to remember is that just because you can put HTML in your Markdown, doesn't really mean you should. It's more meant for an occasional snippet here and there, when there's something small that you can't quite do in pure Markdown.

So, this issue of having the HTML slow things down isn't something you should normally encounter anyway.

It the past i was a Wordpress specialist, but have an love/hate relation with WP

Yep. I try to stay away from WordPress if I can. I've helped people with it too, but I'm far from an expert. Theming WordPress seemed FAR too complicated to ever bother with. And that's, of course, why I like Pico so much. You can use as much of as little of it as you want, and the rest is just HTML!

Lately I've been comparing Pico to just being "glue" that "binds together Markdown, Yaml, Twig, and HTML/CSS". As long as you're familiar with those four things, there's not much else to it. Pico just stays out of your way. It acts as a bridge between those technologies, and then lets you do whatever you want with them. 😁

@ctuxboy
Copy link
Author

ctuxboy commented May 6, 2022

@mayamcdougall in my project it is not a priority adding html in the md-file's content, so thats good news 😃

Yep. I try to stay away from WordPress if I can. I've helped people with it too, but I'm far from an expert. Theming WordPress seemed FAR too complicated to ever bother with. And that's, of course, why I like Pico so much. You can use as much of as little of it as you want, and the rest is just HTML!

Yes it is a lot of work and sometimes when updating themes/plugins the WP-site is broken 😒

Lately I've been comparing Pico to just being "glue" that "binds together Markdown, Yaml, Twig, and HTML/CSS". As long as you're familiar with those four things, there's not much else to it. Pico just stays out of your way. It acts as a bridge between those technologies, and then lets you do whatever you want with them. 😁

Thats a fantastic explanation and 100% agree 👍 , I feel that Pico give me a lot of freedom building themes and snippets. Okay i know, my pico-knowledge it's at the beginning now, but i learn fast and start buidling my first theme with ease, and motivates how easy it is 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants