Search code examples
recursionrazorumbracoumbraco7

Getting Children recursively for a treeview generation in a template


Here is how my structure is setup: I have many Services (DocType) which hold children Documents (DocType). The Document DocType can have other Document elements as its children.

This way, it's possible for editors to create that kind of tree: Service > Document> Document >Document > Document

Documentation center structure

There's no limits to the amount of levels this can go on for.

I'm trying to find a way to loop recursively through each Document and their descendants, but they need to be nested within eachother. This is where I'm having trouble.

I can't seem to find a simple way to loop recursively through each levels per children to generate the content.

Here is what I have so far:

@{ var selection = Model.Children.Where("Visible");
     if (selection.Any()) {
        <ul>
            @foreach (var item in selection) {
            <li>
                <div class="ServiceDocCenterDocumentation">
                    <h4><a href="#" id="[email protected]" class="DocCenterDocumentationTitle">@item.Name</a></h4>
                    <div class="DocCenterDocumentationDescription">@item.GetPropertyValue("bodyText")</div>
                </div>
                <div>
                    @foreach (var children in item.Descendants()){
                        @* This is the part I'm struggling with for the last few days *@

                        var actualChildren = children;
                        <h5><a href="#" id="[email protected]" class="DocCenterDocumentationTitle">@children.Name</a></h5>
                        <div class="DocCenterDocumentationDescription">@children.GetPropertyValue("bodyText")</div>
                    }
                </div>
            </li>
            }
        </ul>

    }
    }

Here's what I would like to achieve (recursively, not manually):

<div class="myService">
    <div class="Documents">
        <div class="[email protected]">
            @elem.bodyText
            foreach (var child in elem.Children){
                <div class="[email protected]">
                    @child.bodyText
                    foreach (var grandchild in child.Children){
                        @* It goes on and on for the amount of levels available *@
                    }
                </div>
            } 
        </div>
    </div>
</div>

I'm still not very fluent in Razor, and I'm wondering if there's a way of achieving what I would like to achieve.


Solution

  • You should use Razor helpers to create your recursive function.

    Apart of that, you shouldn't use Descendants in each children as it would return every node below this one. You just need its children, then the children of each children and so forth. So you will pass each children list to the function to display it.

    @{ var selection = Model.Children.Where("Visible");
         if (selection.Any()) {
            <section class="DocCenter">
                @foreach (var item in selection) {
                    <section class="Service">
                        <div class="ServiceDocCenterDocumentation">
                            <h1><a href="#" id="[email protected]" class="DocCenterDocumentationTitle">@item.Name</a></h1>
                            <div class="DocCenterDocumentationDescription">@item.GetPropertyValue("bodyText")</div>
                        </div>
    
                        @DisplayChildren(item.Children().Where("Visible"))
    
                    </section>
                }
            </section>
    
        }
    }
    
    @helper DisplayChildren(IEnumerable<IPublishedContent> children){  
        if(children.Any()){  
            <section class="children">
                foreach(var child in children){
                    <section class="[email protected]">
                        <div class="DocumentDetails">
                            <h1><a href="#" id="[email protected]" class="DocCenterDocumentationTitle">@children.Name</a></h1>
                            <div class="DocCenterDocumentationDescription">@children.GetPropertyValue("bodyText")</div>
                        </div>
                        //Recursive call here
                        @DisplayChildren(child.Children().Where("Visible")) 
                    </section>
                }
            </section>
        }
    }
    

    On the other hand, although is not related to the recursive question, I would use the <section> element to separate parents and children, that way you can also use h1 for all the headings as they are created in a section hierarchy: Using HTML sections and outlines