Jump to content

How to get grand childs


wmushtaq
 Share

Recommended Posts

@Macrura & @Ivan: Thank you very much for your kind help and support. It is highly appreciated.  :-)

if i was using pages for images, I would keep the images template published and not hidden, but i wouldn't make a php file for it.

you can easily find your featured images by using this:

$featured_images = $pages->find("template=image, featured=1");

I have made the template without the php file merely as a placeholder for related fields *edit/* and a lot of additional fields for data besides the image field,  */edit* and most of those are reused (ProcessWire News & Updates (November 18) ::  Using ProcessWire fields efficiently) elsewhere on the site too.

Also, I have not published the pages to avoid a 404 error (it would throw a 404 anyways as the templates does not have a php file associated, I just liked the strike-out, it gives me the satisfaction/ visual clue that these are just for internal use and private) and set hidden to avoid them getting listed in searches etc

Your code works but is not specific to a page, I have taken the liberty to incorporate it into @Ivan's code as below:

@Kay Lohn: Your "...limit=3" selector fires several times - as many, as there are children under "Projects". And each time it limits maximum number of featured photoes under that category. So you can output a maximum of (3 x NumberOfProjectsChildrenPages) featured photo pages.

As Macrura suggested there are better/simplier ways to do what you intend to, like:

<?php foreach ($pages->find("template=photo_page_template, include=hidden, sort=-date, limit=3")) { ?>
	<a href="<?php echo $child->link_to_page->url; ?>">
		<img src="<?php echo $child->my_image->url; ?>" alt="<?php echo $child->image_desc; ?>"/>
	</a>
<?php }; ?>

Where photo_page_template is the template of you photo pages.

This piece of code below worked magic:

<?php foreach ($pages->find("template=photo_page_template, include=all, is_featured=1, sort=-date, limit=3") as $child) { ?> // include=all because I have these pages unpublished AND hidden as include=hidden would not return the set of pages that are unpublished

From what I understood from the code line above in essence, is that, I am asking ProcessWire/ Wire/ API to (kindly correct me if I am wrong)

'find me a set of three pages from all pages on the wire including hidden and unpublished pages whose template is photo_page_template and its is_featured value is true, sort them by latest first by date and return me the set/ results as a PageArray and then iterate over the array and give me each element as a variable, $child, so I can get the field values I want from that page'

So far EXCELLENT! The code works!

But... the problem with above is that it is not specific to a page whose children or grandchildren I require. Currently in my project I do not yet have any other pages under root (Home) except Projects, which have children AND/OR children of its children. What if there was another page or pages for e.g. let's say, for one,  Events under root (Home) which had children just like the Projects page and had featured items but none of the featured from it are needed on the homepage? I have not yet tried it out but do you think by adding parent=/events/ (assuming the name of the page as events) would give me the proper required set as in the code below? 

<?php foreach ($pages->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3") as $child) { ?> // trying to get a specific set of pages under Events page 

...and just to contribute to the original topic. I use this function to get all subpages of a page:

function getAllDescendants ($root, $prependRoot) {
	$descentants = new PageArray();
	foreach ($root->children() as $child) {
		$descentants->add($child);
		if ($child->numChildren()) {
			$descentants->add(getAllDescendants($child));
		}
	}
	if ($prependRoot) $descentants->prepend($root);
	return $descentants;
}

I include this function in _functions.php file which prepends every template file and use it like this:

<?php
$listingPages = getAllDescendants($pages->get('some selector'))->find("some selector");
foreach($listingPages as $listingItem) {
	...
};
?>

Thanks a lot Ivan for this. I haven't used it yet but will surely try it out and let you know. BTW, where can I find the _functions.php file? Thank you for the Delayed Output link, I have already adopted the strategy to some extent (to some extent because at times i need a quick and dirty output or I just feel lazy :(  to add it to my includes) and adapted/ improvised in my current project, I will be adding your function to my own custom helper functions 'inc' file, it might come in handy.

Once again, thank you very much for your kind help and support.  :-)

Link to comment
Share on other sites

This code

<?php foreach ($pages->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3") as $child) { ?>

will not work - it will only give you the direct children of /events/. The whole topic is about how to get all subchildren and/or only grandchildren, so read above ))

If you incorporate my little function, you could do what you intend like this:

<?php
$listingPages = getAllDescendants($pages->get(/events/))->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3");
foreach($listingPages as $listingItem) {
	...
};
?>

This way you only get featured photoes from under just the selected parent.

You could just place the function in the template file before the actuall call to it. To have it to my disposal at any template I put it in _functions.php, which could be prepended to every template file by putting this line in the site/config.php file of your installation:

$config->prependTemplateFile = './includes/_functions.php';

where "/includes/_functions.php" is the path to the file relative to templates directory.

Link to comment
Share on other sites

Yes you are right, the code below fails to give the expected results.

<?php foreach ($pages->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3") as $child) { ?> // Code returns the direct children of page 'events' NOT the grandchildren

Your provided function gives an error: 

Missing argument 2 for getAllDescendants(), called in ... _functions.php on line ... and defined

My function call is:

$children = getAllDescendants($pages->get('/events/'))->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3");

Your function as in my _functions.php file:

function getAllDescendants ($root, $prependRoot) {
	$descentants = new PageArray();
	foreach ($root->children() as $child) {
		$descentants->add($child);
		if ($child->numChildren()) {
			$descentants->add(getAllDescendants($child));
		}
	}
	if ($prependRoot) $descentants->prepend($root);
	return $descentants;
}

What value should be passed for $prependRoot? What am I doing wrong? *stumped*

Link to comment
Share on other sites

Think I found a solution to the problem, thanks to this reply from ryan.

If you wanted all grandchildren, and nothing deeper, you could do this:
 

$page->find("parent=$page->children");
If you wanted grandchildren and anything deeper, you could do this:

$page->find("parent!=$page");
If you wanted children, grandchildren and anything deeper, you could do this:

$page->find('');

To get the grandchildren of a specific page, here is what I did:

<?php 
// Get a specific set of grandchildren pages under Projects page
$parent_page = wire('pages')->get('/projects/'); // Define a variable and the parent page whose grandchildren you want to get, here I need it from the page 'projects' hence get('/projects/')
foreach ($pages->find("template=photo_page_template, parent={$parent_page->children}, include=all, is_featured=1, sort=-date, limit=3") as $child) { // This part parent={$parent_page->children} is what does the magic here ?> 

	<a href="<?php echo $child->link_to_page->url; ?>">
	    <img src="<?php echo $child->my_image->url; ?>" alt="<?php echo $child->image_desc; ?>"/>
	</a>
<?php }; ?>

It works! Tested with newly created 'Events' page in my project structured as follows:

Home

  |- Projects // name=projects

     |- Nature

        |- Featured Image // Hidden & Unpublished, has a checkbox 'featured', checked

        |- Another Image // Hidden & Unpublished, has a checkbox 'featured', unchecked

        |- Another Image //Hidden & Unpublished, has a checkbox 'featured', unchecked

     |- People

        |- Featured Image // Hidden & Unpublished, has a checkbox 'featured', checked

        |- Another Image // Hidden & Unpublished, has a checkbox 'featured', unchecked

  . . .

  |- Events // name=events

     |- Photography 101

        |- Featured Image // Hidden & Unpublished, has a checkbox 'featured', checked

        |- Another Image // Hidden & Unpublished, has a checkbox 'featured', unchecked

        |- Another Image //Hidden & Unpublished, has a checkbox 'featured', unchecked

     |- Advanced Photography

        |- Featured Image // Hidden & Unpublished, has a checkbox 'featured', checked

        |- Another Image // Hidden & Unpublished, has a checkbox 'featured', unchecked

 . . .

By changing:

$parent_page = wire('pages')->get('/projects/'); to $parent_page = wire('pages')->get('/events/');

only the results from the 'events' page were returned :)

I have worked up a rough function to get the grandchildren of a page, it works perfectly in my case, excuse the code-smell. Usage in comments.

<?php
/*
 * Function to get grandchildren of any page
 * 
 * Required arguments are $parent_page and $template_name
 * 
 * NoteToSelf: REMEMBER: This function returns grandchildren of a page a.k.a children of children, so
 * if you specify '/' as parent and 'some_template' as template, it will return an array of 2nd level pages
 * i.e. Home->sub-page->sub-page
 * 
 * Argument passing example:
 * For $parent_page pass as '/' for Home/root or '/somepage/' for some other page
 * For $template_name pass as 'template_name'
 * [OPTIONAL] For $include pass as 'all' or 'hidden' etc [read ProcessWire Docs for more]
 * [OPTIONAL] For $sort pass as 'date' or '-date' etc [read ProcessWire Docs for more]
 * [OPTIONAL] For $limit pass as '0' or '10' or '999999999' etc [read ProcessWire Docs for more]
 * [OPTIONAL] For $custom_field_check pass as 'field=value' or 'is_featured=1' etc [read ProcessWire Docs for more]
 * 
 * To skip optional arguments pass null
 * 
 * Usage example using null:
 * 
 *  $gc = getGrandChildren('/', 'my_template', null, '-date', '3', null);
 *      if ($gc) {
 *          foreach ($gc as $child) {
 *              echo $child->title . '<br />' . $child->name . '<br />';
 *          }
 *      }
 * 
 * Usage example using all arguments passed to the function:
 * 
 *  $gc = getGrandChildren('/projects/', 'photo_page_template', 'all', '-date', '3', 'is_featured=1');
 *      if ($gc) {
 *          foreach ($gc as $child) {
 *              echo $child->title . '<br />' . $child->name . '<br />';
 *          }
 *      }
 * 
 * Kay Lohn | https://processwire.com/talk/user/2149-kay-lohn/
 *      
 */
function getGrandChildren($parent_page, $template_name, $include = null, $sort = null, $limit = null, $custom_field_check = null) {
    // Declare variables
    $parent_page = wire('pages')->get("{$parent_page}");
    
    // Initialize variable as a new PageArray()
    $grandchildren = new PageArray();

    // Initialize a blank variable to store query
    $query = "";
    
    // Check and process required values 
    if (!$template_name) // Throw exception if not present
        throw new Exception('Template name is required');
    if ($template_name) // Add to $query if present
        $query .= 'template=' . $template_name;
    if (!$parent_page) // Throw exception if not present
        throw new Exception('Parent Page is required');
    if ($parent_page) // Add to $query if present
        $query .= ', parent=' . $parent_page->children;
    
    // Check and process Optional values
    if ($include) // Add to $query if present
        $query .= ', include=' . $include;
    if ($sort) // Add to $query if present
        $query .= ', sort=' . $sort;
    if ($limit) // Add to $query if present
        $query .= ', limit=' . $limit;
    if ($custom_field_check) // Add to $query if present
        $query .= ', ' . $custom_field_check;
    
    // Run loop
    foreach (wire('pages')->find($query) as $result) {
        $grandchildren->add($result); // Add each result to PageArray
    }

    // Experimental
    if (!empty($grandchildren)) {
        return $grandchildren; // Return the page array
    } else {
        throw new Exception('No results :('); // Throw an error if PageArray was empty
    }
}
?>

I hope this proves useful.

  • Like 1
Link to comment
Share on other sites

Hey thanks for posting this, how to find grand children is something anyone will bump into sooner or later.

Funny though that it needs so much code. I'm gonna wrap my head about it and start digging php scripts

to see if this can't be done with less code. Something similar I guess with getting pictures down the

page tree ($pages). That works with storing the picture first in a temp variable and then use $page.

Guess something similar can be done here.

  • Like 1
Link to comment
Share on other sites

The function seems a little overkill, but why not.

After all to get the grandchildren you only need something like

$grandchildren = $pages->find("parent=$page->children, sort=-modified, limit=20");

That's already a function right there. One can of course wrap it into a function and play around with it.

  • Like 4
Link to comment
Share on other sites

@Soma: My function returns all pages under a page, not only grandchildren. Is there a way to achieve this by selectors only?

@Kay Lohn: You should call the function with both parameters, of course:

$children = getAllDescendants($pages->get('/events/'), false)->find("template=photo_page_template, parent=/events/, include=all, is_featured=1, sort=-date, limit=3");

$prependRoot determins whether the root page will be included in the PageArray returned by the function. Should have put a default value for it anyway. My fault.

Link to comment
Share on other sites

@ivan not sure I follow

$allDescendantsOfEvents = $pages->get('/events/')->find("");

Too many code examples in this thread that makes no sense. :D

You don't want to return ALL descendants to then filter them again by parent etc. In my cases this could return 100k pages it then filters 100 pages from that...

  • Like 2
Link to comment
Share on other sites

A simple function to get the grandchildren of a page (0 to 2 levels deep): (The code below is just an example, be-careful, it does not filter results and would return pages you would not want but can easily be extended to include filtering)

<?php
/*
 * Function to get 0 to 2 levels deep page children (returns results from 'all' pages)
 * Home (Depth 0)
 *  |-Level 1 (Depth 1)
 *      |-Level 2 (Depth 2)
 */
function getChildren($parent, $depth) {
    
    if ($depth > 2 || $depth < 0) throw new Exception ('Depth invalid. Should be 0 or <=2.');
    
    $include = 'all';
    $parent = wire('pages')->get("{$parent}");
    $level1children = $parent->children("parent={$parent}, include={$include}");
    $level2children = $parent->find("parent={$level1children}, include={$include}");
    
    switch ($depth){
    case '0':
        return array($parent);
        break;
    case '1':
        return $level1children;
        break;
    case '2':
        return $level2children;
        break;
    }
}
?>

Going overboard (0 to 5 levels deep)

<?php
/*
 * Function to get 0 to 5 levels deep page children (returns results from 'all' pages)
 * Home (Depth 0)
 *  |-Level 1 (Depth 1)
 *      |-Level 2 (Depth 2)
 *         |-Level 3 (Depth 3)
 *              |-Level 4 (Depth 4)
 *                  |-Level 5 (Depth 5)
 */
function getChildren($parent, $depth) {
    
    if ($depth > 5 || $depth < 0) throw new Exception ('Depth invalid. Should be 0 or <=5.');
    
    $include = 'all';
    $parent = wire('pages')->get("{$parent}");
    $level1children = $parent->children("parent={$parent}, include={$include}");
    $level2children = $parent->find("parent={$level1children}, include={$include}");
    $level3children = $parent->find("parent={$level2children}, include={$include}");
    $level4children = $parent->find("parent={$level3children}, include={$include}");
    $level5children = $parent->find("parent={$level4children}, include={$include}");
    
    switch ($depth){
    case '0':
        return array($parent);
        break;
    case '1':
        return $level1children;
        break;
    case '2':
        return $level2children;
        break;
    case '3':
        return $level3children;
        break;
    case '4':
        return $level4children;
        break;
    case '5':
        return $level5children;
        break;        
    }
}
?>

The function above can easily be extended to filter results. Hope this would prove useful.

@Soma: Thank you for your valuable inputs :) Yes, you are right. The find() function is right there and it can be used directly, but then I'd have to write long long lines each time I need some grandchildren or children or great grandchildren etc.

Comparing this:

<?php 
$parent_page = wire('pages')->get('/projects/'); 
foreach ($pages->find("parent={$parent_page->children}, include=all") as $child) { ?> 
   echo '<li>'.$child->title.'</li>';
<?php }; ?>

to this:

<?php
    $gc = getChildren('/projects/', '2');
    foreach ($gc as $child) {
        echo '<li>'.$child->title.'</li>';
    }
?>

It's obvious how a function in some cases can help ease things up a bit. Building this function to get children etc had proved extremely useful in building my project as I have to deal with pages several levels deep. I hope this help others too. :)

@Ivan: No problem, thanks, I still have your function in my _functions.php file, it never hurts to have two in the arsenal :)

@pwired: Thanks for the 'Like'. Gracias!

Regards use of the above experimental function, what would be the overhead of using it on system resources, site speed, performance etc on a live site?

Link to comment
Share on other sites

  • 2 weeks later...

@ivan not sure I follow

$allDescendantsOfEvents = $pages->get('/events/')->find("");

Too many code examples in this thread that makes no sense. :D

You don't want to return ALL descendants to then filter them again by parent etc. In my cases this could return 100k pages it then filters 100 pages from that...

My function is totally useless. This is the decision I was looking for. Have not done my homework. I never found $page->find() - probably confused it with $pages->find() every time visiting the cheatsheet. I think has_parent selector, which I was deliberately looking for so many times with no success, can also do the job. I need stronger glasses.

Min363401-vi.jpg

  • Like 3
Link to comment
Share on other sites

Going overboard (0 to 5 levels deep)

Or, going completely overboard using recursion, only loading pages to the requested depth: ;)

function getAncestors($pg, $level)
{
    $retPages = (new PageArray())->add($pg);
    if( $level > 0 )
    {
        foreach( $pg->children As $child )
            $retPages->add(getAncestors($child, $level - 1));
    }
    return $retPages;
}
  • Like 2
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...