Very good like always 😉
Create a dynamic category list
-
I have to admit, @DownPW gets the credit for this, as I saw something he was working on for his release of v3 and liked it so much I asked him if I could “steal” it for my own site.
I took his original design concept and extended it using the NodeBB API. In Harmony, it looks like this (see resultant menu on the left underneath the categories button).
Now I have you salivating I expect you’d like the code to test for yourself? Sure…
Firstly, you’ll need to go to
/admin/settings/navigation
Locate the Categories icon as shown below, and enable the “Dropdown” feature
Save
Now add the below code into the section shown
<li id="thecategories"><span class="category-menu"><i class="fa fa-fw fa-list"></i><a style="display: inherit;" class="dropdown-item rounded-1" href="/categories">All Categories</span></a></li>
Save your changes
Now to go
/admin/appearance/customise#custom-js
Paste this code into the editor
$(document).ready(function() { $.getJSON('/api/categories', function(data, status) { $.each(data.categories, function(key, value) { var categorylist = $(" \ <li><span class='category-menu'><i class='fal " + this.icon + "'></i><a style='display: inherit;' class='dropdown-item rounded-1' href='/category/" + this.slug + "'>" + this.name + "</a></span></li> \ <ul style='list-style: none;'>" + this.children.map(c => `<li><span class='category-menu'><i class='fal ${c.icon}'></i><a class='dropdown-item rounded-1' style='display: inherit;' href='/category/${c.slug}'>${c.name}</a></span></li>`).join(" ") + "</ul><li class='dropdown-divider'></li>" ); if ($(window).width() < 767) { $(".bottombar #thecategories").append(categorylist); } else { $(".sidebar-left #thecategories").append(categorylist); } }); }); });
Save the changes
Now go to
/admin/appearance/customise#custom-css
Add the below code into the CSS console
li#thecategories { min-width: 250px; padding-left: 10px; font-size: 90%; padding-right: 10px; } span.category-menu { display: inline; margin-left: 10px; margin-right: 10px; }
Note, you’ll likely need to adjust this CSS to suit your own site and layout.
NOTES: It’s important to detail here that the JS function does all of the work for you, but will include any icons you’ve defined for categories etc. If you don’t use icons, you should remove the below sections
<i class='fal " + this.icon + "'></i>
Of equal importance is the below if/else
if ($(window).width() < 767) { $(".bottombar #thecategories").append(categorylist); } else { $(".sidebar-left #thecategories").append(categorylist); }
This determines the size of the viewport in use (mobile versus desktop) and then based on the output from the device, determines where to append the dynamic output - either the
sidebar
or thebottombar
). As this is effectively choosing a location, we can use the same id of#thecategories
twice as it’s “one or zero” - either one, but not both - in a nutshell, you cannot declare the sameID
twice or this will cause you problems.As this code uses the API, it never needs to be updated - if you remove or add any categories, these are reflected as soon as you reload the browser. So we don’t kill the CPU or the website itself, this code is only called
onLoad
and does not trigger using anyajax
callbacks.Enjoy.
-
-
Ouaaaaaa Very good work my friend. You worked like crazy to offer us this;) I will test this as soon as possible;)
Love this functionnality !
@phenomlab said in Create a dynamic category list:
I have to admit, @DownPW gets the credit for this, as I saw something he was working on for his release of v3 and liked it so much I asked him if I could “steal” it for my own site.
I took his original design concept and extended it using the NodeBB API. In Harmony, it looks like this (see resultant menu on the left underneath the categories button).And as I told you in DM, seeing how you help us, my codes are yours, no problem for that
Big up !
-
@DownPW thanks. Really happy with how this turned out to be honest!
-
Ps - if anyone likes the theme in the screenshot, it’s called “Blackout” and will be featured in V3 of sudonix when released.
-
@DownPW has just pointed out a valid issue here. If you have lots of categories, then the list can easily extend the browser window, and if you resize the browser, you won’t be able to access it properly. For this to work, you’d need to apply custom CSS to that - for example, the below, but it should only be applied at a minimum of
1200px
so will need to use a media class.li#thecategories { min-width: 250px; font-size: 90%; overflow: auto; height: calc(100vh - 110px); }
This specific CSS calculates the height of the browser window, then removes some of it to allow for the bottom bar in desktop view (note that you might not be using this, so you’ll need to adjust the
calc
value as you see fit). -
I’ll revise the original post to reflect this
-
I’ve been playing around with this today, and created a “tree” view
-
If you’d like the code for this, then it’s below (it’s essentially a “fork” of the original to allow for extra classes)
JS
$(document).ready(function() { $.getJSON('/api/categories', function(data, status) { $.each(data.categories, function(key, value) { var categorylist = $(" \ <li class='dropdown-item tree-root'><span class='category-menu'><i class='fal " + this.icon + "'></i><a style='display: inherit;' class='dropdown-item rounded-1' href='/category/" + this.slug + "'>" + this.name + "</a></span></li> \ <ul class='tree-branch' style='list-style: none;'>" + this.children.map(c => `<li class='dropdown-item tree-node'><span class='category-menu-tree-node'><i class='fal ${c.icon}'></i><a class='dropdown-item rounded-1' style='display: inherit;' href='/category/${c.slug}'>${c.name}</a></span></li>`).join(" ") + "</ul>" ); if ($(window).width() < 767) { $(".bottombar #thecategories").append(categorylist); } else { $(".sidebar-left #thecategories").append(categorylist); } }); }); });
CSS
ul.tree-branch { border-left: 1px solid var(--bs-border-color); margin-left: 22px; } li.tree-node:before { border-bottom: 1px solid var(--bs-border-color); position: relative; top: -0.3em; height: 1em; width: 30px; content: ""; display: inline-block; left: -48px; } span.category-menu-tree-node { margin-left: -35px; }
NOTE: I use variables for all of my CSS, so you’ll need to substitute
var
with your ownHEX
values. Also note, that you still need the original CSS from the first post for this to work - what is listed above only provides you with the tree, branches, and nodes effect. -
This looks so cool, thanks @phenomlab @DownPW
-
@phenomlab is there something wrong with the category list panel?
edit: it is fixed now. When I minimize the screen size on my computer and then expand it again, the panel goes blank. But it is fixed when I refresh the page, I guess this is something expected.
-
@crazycells No, not at all. I can’t reproduce this, but that does sound like a hardware acceleration issue. What happens if you disable that (I’m assuming Chrome here if of course, but the same applies for other browsers)
https://pureinfotech.com/disable-hardware-acceleration-chrome/
-
@phenomlab sorry, my mistake, it is fixed now.
I am using Firefox, but this morning I needed to restart to update Firefox, so I believe I was having the problem yesterday night because of the outdated browser. -
@crazycells Good. Glad to hear it’s an even simpler fix
-
-
Just circling back here as I’ve been helping @cagatay this morning on his site, and noticed that if you use a mixture of
fa-brands
andfa-solid
then the code supplied above may produce some odd looking results.If this is the case, replace the function with this
$(document).ready(function() { $.getJSON('/api/categories', function(data, status) { $.each(data.categories, function(key, value) { var iconClass = 'fa'; // Default to 'fa' if the icon type is not recognized // Check if the icon is FontAwesome Unicode if (this.icon.startsWith('&#x') || this.icon.startsWith('')) { iconClass = 'fa'; } else if (this.icon.startsWith('fab')) { iconClass = 'fab'; } var categorylist = $(" \ <li class='dropdown-item tree-root'><span class='category-menu'><i class='" + iconClass + " " + this.icon + "'></i><a style='display: inherit;' class='dropdown-item rounded-1' href='/category/" + this.slug + "'>" + this.name + "</a></span></li> \ <ul class='tree-branch' style='list-style: none;'>" + this.children.map(c => { var childIconClass = 'fa'; // Default to 'fa' for child icons // Check if the child icon is FontAwesome Unicode if (c.icon.startsWith('&#x') || c.icon.startsWith('')) { childIconClass = 'fas'; } else if (c.icon.startsWith('fab')) { childIconClass = 'fab'; } return `<li class='dropdown-item tree-node'><span class='category-menu-tree-node'><i class='${childIconClass} ${c.icon}'></i><a class='dropdown-item rounded-1' style='display: inherit;' href='/category/${c.slug}'>${c.name}</a></span></li>`; }).join(" ") + "</ul>" ); if ($(window).width() < 767) { $(".bottombar #thecategories").append(categorylist); } else { $(".sidebar-left #thecategories").append(categorylist); } }); }); });
In fact, if you want to replace it anyway to make your experience “future proof”, you can use this code now
-
I never really liked the way this was generated, as it didn’t exactly match the other dropdowns. I’ve since rectified that here, and will now update GIT.
Revised navigation code
<li class='nav-item' title=''> <a class='nav-link nav-btn navigation-link px-3 py-2' href='/categories'> <span class='d-inline-flex justify-content-between align-items-center w-100'> <span class='text-nowrap'> <i class='fa-regular fa-list fa-solid menu-icon' data-content='' aria-hidden='true'> </i> <span class='nav-text px-2 fw-semibold'>All Categories</span> </span> <span component='navigation/count' class='badge rounded-1 bg-primary hidden'></span> </span> </a> <ul class='tree-branch' style='list-style: none;'></ul> </li> <li id='thecategories'><h6 class='dropdown-header text-xs'>Individual Categories</h6></li>
Revised category list code
$(document).ready(function() { $.getJSON('/api/categories', function(data, status) { $.each(data.categories, function(key, value) { var iconClass = 'fa-regular'; // Default to 'fa-regular' if the icon type is not recognized // Check if the icon is FontAwesome Unicode if (this.icon.startsWith('&#x') || this.icon.startsWith('')) { iconClass = 'fa-regular'; } else if (this.icon.startsWith('fab')) { iconClass = 'fab'; } var categorylist = $("<li class='nav-item' title=''> \ <a class='nav-link nav-btn navigation-link px-3 py-2' href='/category/" + this.slug + "'> \ <span class='d-inline-flex justify-content-between align-items-center w-100'> \ <span class='text-nowrap'> \ <i class='" + iconClass + " " + this.icon + " menu-icon' data-content='' aria-hidden='true'></i> \ <span class='nav-text px-2 fw-semibold'>" + this.name + "</span> \ </span> \ <span component='navigation/count' class='badge rounded-1 bg-primary hidden'></span> \ </span> \ </a> \ <ul class='tree-branch' style='list-style: none;'>" + this.children.map(c => { var childIconClass = 'fa-regular'; // Default to 'fa-regular' for child icons // Check if the child icon is FontAwesome Unicode if (c.icon.startsWith('&#x') || c.icon.startsWith('')) { childIconClass = 'fa-regular'; } else if (c.icon.startsWith('fab')) { childIconClass = 'fab'; } return `<li class='nav-item tree-node' title=''><a class='nav-link nav-btn navigation-link px-3 py-2' href='/category/${c.slug}'><span class='d-inline-flex justify-content-between align-items-center w-100'><span class='text-nowrap'><i class='${childIconClass} ${c.icon} menu-icon' data-content='' aria-hidden='true'></i><span class='nav-text px-2 fw-semibold'>${c.name}</span></span><span component='navigation/count' class='badge rounded-1 bg-primary hidden'></span></span></a></li>`; }).join(" ") + "</ul> \ </li>"); if ($(window).width() < 767) { $(".bottombar #thecategories").append(categorylist); } else { $(".sidebar-left #thecategories").append(categorylist); } }); }); });
Minor CSS changes (only the styles listed below)
ul.tree-branch { border-left: 1px solid var(--bs-link-color); margin-left: 22px; } li.tree-node:before { border-bottom: 1px solid var(--bs-link-color); position: relative; top: 1.4em; width: 35px; content: ""; display: flex; left: -33px; }
-
Hmm - seems I never committed this code. I’ll do that now…
EDIT - here it is