Adding a page that use a dynamic content

Create the page to use the template linktree as

---
template: linktree
title: Ideas
process:
    twig: true
access:
    site.login: true
---

Put the data in a directory _data as default.md as an example as frontmatter

---
title: Data Container
tree_items:
    - title: Business
      children:
          - title: Seniors
            children: 
                - title: "Pickup Up Stuff"
                  url: "https://pragprog.com"
                  details: "Foo Blah..."
    - title: "To Watch"
      children:
          - title: "Grav Tutorials"
            details: "Official playlist..."
---

The template listtree.html.twig loads the _data/default.md front matter as treeData and uses in javascript to create tree and details

{% extends 'partials/base.html.twig' %}

{% block content %}

{# 1. Find the sub-page relative to the current page #}
{# Note: use the folder name with the underscore #}
{% set relative_path = page.route ~ '/_data' %}
{% set data_page = page.find(relative_path) %}

{% if data_page %}
    {# 2. Get the pre-parsed array from the header #}
    {% set clean_array = data_page.header.tree_items %}
    <script>
   // 3. Convert the PHP array to a JavaScript Object
        var treeData = {{ clean_array|json_encode|raw }};

        console.log("Success! Data loaded:", treeData);

        // Now you can build your tree using JS
        // buildTree(treeData);
    </script>

{% else %}
    <div class="alert">Error: Could not find _data page.</div>
{% endif %}

    <div class="split-container">
    <aside class="sidebar-panel" id="js-sidebar"></aside>
    <main class="content-panel" id="js-content">
        <div class="welcome-box">
            <p>Select an item from the tree to view details.</p>
        </div>
    </main>
</div>

<script>
document.addEventListener("DOMContentLoaded", function() {
    // 1. Initial Check: Ensure data exists
    if (typeof treeData === 'undefined' || !treeData) {
        console.error("treeData is missing. Make sure the Twig loaded correctly.");
        return;
    }
    const sidebar = document.getElementById('js-sidebar');
    const content = document.getElementById('js-content');

    // 2. Start the Build
    const rootList = createTreeList(treeData);
    sidebar.appendChild(rootList);

    // --- Core Function: Recursively Build the List --- //
    function createTreeList(items) {
        const ul = document.createElement('ul');
        ul.className = 'tree-list';

        items.forEach(item => {
            const li = document.createElement('li');
            li.className = 'tree-item';

            // A. Create the Row (Toggle + Title)
            const row = document.createElement('div');
            row.className = 'tree-row';

            // B. Check for Children
            const hasChildren = item.children && item.children.length > 0;

            // C. Create Toggle Button
            if (hasChildren) {
                const toggle = document.createElement('span');
                toggle.className = 'tree-toggle';
                toggle.innerHTML = '<i class="fa fa-chevron-right"></i>';

                // Toggle Event
                toggle.onclick = function(e) {
                    e.stopPropagation(); // Prevent triggering the link click
                    li.classList.toggle('open');
                };
                row.appendChild(toggle);
            } else {
                // Spacer for alignment
                const spacer = document.createElement('span');
                spacer.className = 'tree-spacer';
                row.appendChild(spacer);
            }

            // D. Create the Link/Title
            const link = document.createElement('span');
            link.className = 'tree-link';
            link.innerText = item.title;

            // Link Click Event -> Render Details
            link.onclick = function() {
                // Highlight active state
                document.querySelectorAll('.tree-link').forEach(el => el.classList.remove('active'));
                link.classList.add('active');

                // Render Right Panel
                renderDetails(item);
            };
            row.appendChild(link);

            // Append Row to LI
            li.appendChild(row);

            // E. Recursion: Build Children if they exist
            if (hasChildren) {
                const wrapper = document.createElement('div');
                wrapper.className = 'tree-children-wrapper'; // Hidden by CSS until 'open' class added

                const childUl = createTreeList(item.children); // RECURSION HERE
                wrapper.appendChild(childUl);
                li.appendChild(wrapper);
            }

            ul.appendChild(li);
        });

        return ul;
    }

    // --- Helper: Render the Right Panel --- //
    function renderDetails(item) {
        // Clear current content
        content.innerHTML = '';

        // 1. Title
        const h1 = document.createElement('h1');
        h1.innerText = item.title;
        content.appendChild(h1);

        // 2. Details (HTML/Text)
        if (item.details) {
            const desc = document.createElement('div');
            desc.className = 'detail-body';
            // Note: If your YAML has HTML tags (like <br>), use innerHTML. 
            // If strictly text, use innerText for security.
            desc.innerHTML = item.details; 
            content.appendChild(desc);
        }

        // 3. Link Button
        if (item.url) {
            const btn = document.createElement('a');
            btn.href = item.url;
            btn.target = "_blank";
            btn.className = "button button-primary"; // Use standard Grav button classes
            btn.innerHTML = 'Visit Link <i class="fa fa-external-link"></i>';
            btn.style.marginTop = "20px";
            btn.style.display = "inline-block";
            content.appendChild(btn);
        }
    }

});
</script>

{% endblock %}