Showcase + Editor: Unlimited 📁 Folder System in New Responsive Engine

Hey, does anyone know a solution for unlimited folder nesting like this in the new responsive engine?

I want to go unlimit deep like bubble does in the left area of the editor:

The only ideas i have is to implement a reusable element in each other, but i think this is forbidden.

Anyone did this before?

Create two. Main pages calls reusable element subfolder 1. And that calls subfolder 2 and that calls sub folder one. Will be slow…

Hey @buero :wave:

I am going to try to do a blog post about it soon. Here is the beginning of it.

It’s on my eLearning Hub which is a paid membership though: NoCodeMinute - eLearning Hub

I can let you know when the whole post comes out if you want. At least you can see how this part works for now.

At the moment, it is just adding an offset to the front of the element. I still need to figure out the complexity of putting things in order as well as some other things too.

1 Like

To abuse The Good Place: “Instantiating recursive elements in the DOM are like tigers. They are bad!”.

The only way to gain some speed is to structure your thing and your repeating group so that you can exploit depth first ordering to maintain linearity…And then you have to manually implement the algorithm. My rough take using Bubble list operators:

  1. Do a search for all root things, that is things that are their own parents.
  2. Populate the repeating group with this list.
  3. While browsing the repeating group the user selects an un-selected item in the repeating group.
  4. Do a search for that items direct descendent child things.
  5. Split the repeating group’s list into items until selected item, and items from immediately after selected item.
  6. First merge the items until with the childern.
  7. Then merge that list with items from
  8. Finally repopulate your repeating group with this new list

Notes

  1. When a user un-selects a selected item you will have to reverse this process using minus list
  2. It is up to the developers discretion how to visually represent depth, e.g. with indentation.
  3. I have NOT tested out whether or not merge preserves the order of concatenation. As a backup I have sent a note to @Keith, the master of all things List Shifter, to find out if his plugin can support depth first ordering.
  4. You could almost one-line the Bubble expression to build the list as “Repeating group’s list of things: items until # Current cell’s index Merged with Search for thing Merged with Repeating group’s list of things: items from # Current cell’s index+ 1”, except for that last pesky “+1” to the “Current cell’s index”.
2 Likes

Hey @buero :wave:

I did a small sample to see if this is what you were looking for. Check this out:

Unlimited Nesting on Bubble

I’m not sure if this setup is really good for scale though. It uses a list of users to hide the values. I still haven’t figured out how to get it to hide the ones below it in a clean way. Maybe exploring a plugin might work. Hope that helps! :blush:

I think maybe using set states would be a better option. But hopefully this gets you in the right direction.

2 Likes

I had to do some workaround - but i did it with only one RG!!!

How it looks:

Thx Aaron - your idea got me really close to solution and 8h later i got it to work. :slight_smile:

Editor Link:

Preview Link:

Let me know what you think guys.

Any idea for better performance?

3 Likes

@buero Great job!! I think that you are knocking it out of the park! :smiley:

1 Like

you did a blogpost out of it? :slight_smile: you can use mine

I like that solution but it gave me issues when wanting to hide certain folders. Also it would show the loading animation for a split second as it had to search a bunch. So I did a variation of this. Just place that entire thing into an html element, the JSON, is just a sample of the bubble database which you can transform easily, then setup event listeners to actually trigger actions in bubble or that html is setup to change the ‘category’ url param, so you can use that.

a {
text-decoration: none;
}

/**

  • Hidden fallback
    */
    [hidden] {
    display: none;
    visibility: hidden;
    }

/**

  • Styling navigation
    */
    header {
    margin-right: 5px;
    margin-left: auto;
    max-width: 22.5rem;
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
    }

ul, ol {
display: block;
padding-left: 20px;
}

/**

  • Styling top level items
    /
    .nav a, .nav label {
    display: block;
    padding: 0.85rem;
    color: #545969; /
    Font color /
    background-color: transparent;
    box-shadow: inset 0 -1px #1d1d1d;
    transition: all 0.25s ease-in;
    font-family: ‘Segoe UI’; /
    Font family /
    font-size: 12px; /
    Font size /
    font-weight: 600; /
    Font weight /
    }
    .nav a:focus, .nav a:hover,
    .nav label:focus,
    .nav label:hover {
    color: #404452; /
    Font color */
    background: transparent;
    }

.nav label {
cursor: pointer;
}

.nav__list input[type=checkbox]:checked + label + ul {
/* reset the height when checkbox is checked */
max-height: 1000px;
}

/**

  • Rotating chevron icon
    */
    label > span {
    float: right;
    transition: transform 0.65s ease;
    }

.nav__list input[type=checkbox]:checked + label > span {
transform: rotate(90deg);
}

.nav__list ul {
height: 100%;
margin-left: 0;
padding-left: 1;
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease-in-out;
}

/**

  • Display the list when the checkbox is checked
    */
    .nav__list input[type=checkbox]:checked ~ ul {
    max-height: 1000px;
    }
Dynamic Menu /* Add your CSS styles here */
<script>
    // Your JSON data
    const results = [
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",                
            "Nested Count (spacing)": 0,
            "Order": 2,
            "SOP Category Childs": [
                "1688811052709x338905242781351940",
                "1688811083885x505462552484642800"
            ],
            "Title": "Social Media",
            "_id": "1688810258362x893811630213955600"
        },
        {
            "Active Yes/No": true,
            "Modified Date": "2023-07-08T10:10:26.125Z",
            "Order": 2,
            "Title": "Customer Service",
            "_id": "1688811024438x477018805721104400"
        },
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",                
            "Nested Count (spacing)": 2,
            "Order": 3,
            "SOP Category Childs": [
                "1688811164839x195187245402030080"
            ],
            "SOP Category Parent": "1688810258362x893811630213955600",
            "Title": "Social Media - Rules",
            "_id": "1688811052709x338905242781351940"
        },
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",               
            "Nested Count (spacing)": 2,
            "Order": 4,
            "SOP Category Childs": [
                "1688811182090x391550732909674500"
            ],
            "SOP Category Parent": "1688810258362x893811630213955600",
            "Title": "Social Media - Laws",
            "_id": "1688811083885x505462552484642800"
        },
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",                
            "Nested Count (spacing)": 3,
            "Order": 5,
            "SOP Category Parent": "1688811052709x338905242781351940",
            "Title": "Rules- Kings",
            "_id": "1688811164839x195187245402030080"
        },
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",                
            "Nested Count (spacing)": 3,
            "Order": 6,
            "SOP Category Childs": [
                "1688811366238x870957420259573800"
            ],
            "SOP Category Parent": "1688811083885x505462552484642800",
            "Title": "Laws - RRR",
            "_id": "1688811182090x391550732909674500"
        },
        {
            "Active Yes/No": true,
            "Company": "1671226981392x620093986539615600",              
            "Nested Count (spacing)": 4,
            "Order": 7,
            "SOP Category Parent": "1688811182090x391550732909674500",
            "Title": "LAWS RRRR - DDD",
            "_id": "1688811366238x870957420259573800"
        }
    ];

    function transformData(results) {
  let map = {},
    node,
    roots = [],
    i;

  for (i = 0; i < results.length; i += 1) {
    map[results[i]._id] = i;
    results[i].children = [];
  }

  for (i = 0; i < results.length; i += 1) {
    node = results[i];
    if (node['SOP Category Parent'] !== undefined) {
      results[map[node['SOP Category Parent']]].children.push(node);
    } else {
      roots.push(node);
    }
  }
  return roots;
}

function createMenu(categories) {
  let html = '<ul class="nav__list">';

  categories.forEach((category, index) => {
    let formattedId = `group-${category._id}`.replace(/\W/g, '_');
    let padding = category["Nested Count (spacing)"] * 2 || 0; // Each nested level adds 5px
    let url = `bubble.io/categories?category=${encodeURIComponent(category.Title)}`;
    html += `<li>
              <input id="${formattedId}" type="checkbox" hidden />
              <label for="${formattedId}" style="padding-left:${padding}px;" onclick="changePageURL('${url}')"><span class="fa fa-angle-right"></span>${category.Title}</label>`;
    if (category.children && category.children.length > 0) {
      html += createMenu(category.children); // Recursive call for nested categories
    }
    html += '</li>';
  });

  html += '</ul>';
  return html;
}

function changePageURL(url) {
    history.pushState({}, null, url);

}

// Transform data
let transformedData = transformData(results);

// Create menu and add it to the DOM
let menuHTML = createMenu(transformedData);
document.querySelector('.nav').innerHTML = menuHTML;

I will say tho Buero, first off that was a very creative way to go about it like you did, loved it. Also, I LOLd at your naming, thats basically what I do when I get mad lmao.