Understanding isfolder and hasChildren()

Understanding isfolder and hasChildren() in MODX Revolution


People are often confused by the difference between isfolder and hasChildren() in MODX Revolution. Understanding which one is which is important if you want to use them in your web pages.

isfolder is a resource field (which contains either a 0 or a 1), like pagetitle or content. In both MODX Evolution and Revolution, the isfolder field matches the "Container" checkbox on the Create/Edit Resource panel. It contains a 1 if the "Container" checkbox is checked and a 0 if it is not checked.

In MODX Evolution, the isfolder Resource field is set automatically based on whether the resource has children or not. When the first child is added below a Resource in the tree, isfolder is set to 1. When the last child is removed, isfolder is set back to 0. On other words, it is always 1 if the resource has children and always 0 if not.

In MODX Revolution, this is no longer the case. The isfolder field still matches the "Container" checkbox, but it is not set automatically and it is independent of whether a resource has children or not. A Resource with no children can be marked as a container (by checking the checkbox) and a Resource with children can be marked as a non-container (by unchecking the checkbox).

UPDATE

The information above is no longer quite correct. In Revolution, saving a child resource now sets the isfolder field of its parent as it did in Revolution. The field is still independent and can be set manually with the "Container" checkbox, but it will be reset if you save a new child document under that resource.

The plugin below (attached to the OnDocFormSave event) will prevent any resource from being marked as a container. It only executes for new documents, so you can still set the "Container" checkbox when updating an existing resource.

<?php
if ($mode !== modSystemEvent::MODE_NEW) return;

$parent = $resource->getOne('Parent');
    if ($parent) {
    $parent->set('isfolder', false);
    $parent->save();
}

MODX's action to set the parent's isfolder field only happens when a new resource is saved, so you can also set the "Container" checkbox at a later point and it will remain as you set it.

Having isfolder be independent of whether the resource has children or not allows you more flexibility in how container documents are displayed. You might want to insert a Chunk or a Wayfinder menu, for example, only in Resources that are containers, whether or not they have any children yet.

The way to do this is to manually set the "Container" checkbox for any Resource you would like to have treated as a container. You can control what happens in container and non-container Resources with something like this:


[[*isfolder:is=`1`:then=`[[$ContainerChunk]]`:else=``]]

Whatever goes inside the backticks after :then= will be shown if the Resource's "Container" checkbox is checked. Whatever is between the backticks after :else= will be shown if it isn't checked (this could be another chunk). The tag above will be replaced by the "ContainerChunk" Chunk if the "Container" checkbox is checked and by nothing at all if it isn't.

That works fine, and it lets you have complete control over which Resources are treated as containers and which are not, but suppose you want to do things the Evo way and have only Resources with children treated as containers. One solution is to manually check the "Container" checkbox on all of them, but what if you forget?

There are two solutions to this. One is a plugin connected to OnDocFormSave that automatically sets the isfolder field based on whether the Resource has children or not. It's trickier than you might think, because whenever you delete a Resource, you also need to check to see if it has a parent and if there are any siblings left. If it's the last child, you set the parent's isfolder field to 0. You need to do the reverse if you add a new Resource that's a child of another Resource. It's possible, but beyond the scope of this article.

The second method is much easier. It involves the hasChildren() method of a Resource.

hasChildren() to the Rescue

The hasChildren() method of a Resource returns the number of children the Resource has. If it's 0, the Resource is childless. It has nothing to do with the isfolder field or the "Container" checkbox. It simply reports the number of children a Resource has.

This one-line snippet will return that value for you to use with Output Modifiers in your HTML code:

<?php
/* HasChildren snippet */
return $modx->resource->hasChildren() != 0;

The snippet will return a 1 (true) if the Resource has children and a 0 (false) if not.

Suppose you want to show a particular chunk only if a Resource has children and nothing if it doesn't. This code will do it (assuming that you've created the snippet shown just above):


[[!HasChildren:is=`1`:then=`[[$ContainerChunk]]`:else=``]]

Whatever goes inside the backticks after :then= will be shown if the Resource has children. Whatever is between the backticks after :else= will be shown if it doesn't have any children.

You might think that HasChildren is a Custom Output Modifier, but it's not. It's a regular MODX snippet, but we're passing its output to the is built-in Output Modifier.

Why the Exclamation Point?

The exclamation point at the beginning of the snippet tag tells MODX to run the snippet and get a fresh version of the results each time the tag is parsed rather than getting the results from the cache. This is necessary because the last time the snippet was run, it might have been on a different page. The cached version would contain that result, which might not match the result for the current page.

Things to Remember

Because isfolder is a resource field like pagetitle or content, you can show it (or use it in an output modifier) with this tag in your HTML:

hasChildren(), on the other hand, is a method (function) of the Resource object. It can only be used in PHP code, like this:

$numberOfChildren = $modx->resource->hasChildren();

In a plugin rather than a snippet, that code would typically be:

$numberOfChildren = $resource->hasChildren();

The following code will never work, because hasChildren() is not a method of the MODX object:

$numberOfChildren = $modx->hasChildren();

Because hasChildren() returns the number of children, you can do slightly more sophisticated things with it. If you change the HasChildren snippet above to this:

<?php
/* HasChildren snippet */
return $modx->resource->hasChildren();

Now our snippet returns that actual number of children instead of just a 0 or a 1. With this version, you could show a chunk depending on the number of children. For example, this tag will show the chunk only if the current Resources has 3 or more children:


[[!HasChildren:greaterthanorequalto=`3`:then:`[[$SomeChunk]]`:else=``]]

This shorter version does the same thing:


[[!HasChildren:gte=`3`:then:[[$SomeChunk]]:else=``]]

Speeding Things Up

The Output Modifier tag we used above is actually not very efficient because any chunks specified in the tag will be retrieved (and parsed) whether they're going to be used or not.

There are a couple of ways to avoid this. One is to make rewrite the tag so that it only gets chunks that are actually used:


[[[[!HasChildren:is=`1`:then=`$someChunk`:else=``]]]]

This is significantly faster, but it's harder to understand and difficult for some people to write. I prefer to put the logic for getting the chunk in the snippet itself, which is usually faster yet and less prone to error. Let's rewrite our tag and make HasChildren work without any Output Modifiers:


[[!hasChildren? &yes=`SomeChunk` &no=`SomeOtherChunk`]]

Notice that there are no longer any ":" tokens in the tag. No Output Modifiers are being called. This saves time because MODX no longer has to parse the tag for Output Modifiers and locate the correct ones before running them. Now, our snippet would look like this:

<?php
/* HasChildren snippet */
$yesChunk = $scriptProperties['yes'];
$noChunk = $scriptProperties['no'];

if ($modx->resource->hasChildren() != 0) {
    return $modx->getChunk($yesChunk);
} else {
    return $modx->getChunk($noChunk);
}

Our snippet is now getting the names of the yes Chunk and the no Chunk from the properties sent in the snippet tag (which are always in the snippet's $scriptProperties array. After checking to see whether the current Resource has children, it gets and returns only the chunk that's going to be shown.

If you are really concerned about speed, the snippet can be made a few milliseconds faster by compressing it to this one-line form (shown on three lines to make it easier to follow):

<?php
/* HasChildren snippet */
return $modx->getChunk($modx->resource->hasChildren()
    ? $scriptProperties['yes']
    : $scriptProperties['no']);

In this version, the name of the chunk to get is determined inside the parentheses of the getChunk() call. PHP no longer has to take time to set, and later evaluate, the two variables (which no longer exist). This saves both time and memory, though the difference would probably be trivial.



Comments (3)

  1. TobiasMay 01, 2017 at 07:08 AM

    When I was working on a chunk for the rendering of my menu, I stumbled upon this issue. I needed to know whether a specific resource had menu-visible child resources. I found a solution using getResources and If, that might interest people who already have these extras installed.

    I can't paste code here so here's a link: https://gist.github.com/Tobiaqs/ba2aca172ad45083aa092514d51a8fec

  2. Cherie BrushJul 03, 2017 at 09:54 PM

    Thank you! I've been looking for this for a while, but I kept using the wrong search terms. Tonight I thought to include your name in the search, which I should just always do any time I type MODX into Google anyway. :-) I used this plugin on a project ages ago, can't remember which one, and have missed it ever since. It's now in my doc of "things to create when I set up a new MODX site."

    I've said this before, I know, but I just want to thank you again for everything you do for MODX. I don't think I've ever set up a complex MODX site (which is most of them) without referring to your website or your responses on the forum throughout much of the project. I wouldn't have been able to make MODX do half the amazing things I've been able to make it do without your guidance.

    I hope they realize you're one of their most valuable assets!

    Cherie (OCDCoder)

  3. Bob RayJun 12, 2018 at 05:24 PM

    Sorry for the ridiculously long delay. Notifications aren't working for me -- I don't know why.

    @Tobias: Thanks, though using getResources and If is a tremendously inefficient solution. Off the top of my head (i.e., untested), something like this one-line snippet would be infinitely faster:

    [ [!hasVisibleChildren? &parent=`[ [*parent`] ] ] ]
    (take the spaces out of the brackets)

    return $modx->getCount('modResource', array('parent' => $parent, 'hidemenu' => '0'));

    I believe it will return a number if there are visible resources and nothing if not.

    @Cherie: Thanks for the kind words. I really appreciate them.


Please login to comment.

  (Login)