Tree View Show Active Page

This W3Schools tree view JS/CSS/HTML works great for a navigation menu - How To - Tree View
Except, every time a page loads, the tree is completely collapsed.
JS recognizes active page because it bolds text for active page and removes page link.
How can JS also expand tree on the active page to the active page so users can see where they are in the tree?
For instance, on W3Schools example at link above, when “Black Tea” page loads, tree view should expand like this - Beverages > Water Coffee Tea > Black Tea White Tea Green Tea (with Black Tea bolded, no link). Green Tea should not expand.

First a bit of refactoring to allow reuse of the item click function:

function toggleItem(item) {
  item.parentElement.querySelector(".nested").classList.toggle("active");
    item.classList.toggle("caret-down");
}
...
  toggler[i].addEventListener("click", function itemClickHandler(evt) {
    toggleItem(evt.target);
  });

Mark one or more items as being visible:

          <li class="visible">White Tea</li>

Get all of the visible items:

var treeTop = document.querySelector("#myUL");
var visibleItems = treeTop.querySelectorAll(".visible");

Loop through each of those items that should be visible:

visibleItems.forEach(function (item) {
  while (item !== treeTop) {
    item = item.parentNode;
  }
});

When walking up the DOM tree from a visible item, if you find that it’s in a closed group:

function isClosedGroup(el) {
  var isNestedGroup = el.classList.contains("nested");
  if (isNestedGroup) {
    var caret = el.previousElementSibling;
    var isOpenGroup = caret.classList.contains("caret-down");
    return !isOpenGroup;
  }
}
...
  while (item !== treeTop) {
    if (isClosedGroup(item)) {
      ...
    }
    item = item.parentNode;
  }

Then show that group:

function showGroup(item) {
  var caret = item.parentNode.querySelector(".caret");
  toggleItem(caret);
}
...
  while (item !== treeTop) {
    if (isClosedGroup(item)) {
      showGroup(item);
    }
    item = item.parentNode;
  }

Here’s the updated scripting code in full:

function toggleItem(item) {
  item.parentElement.querySelector(".nested").classList.toggle("active");
    item.classList.toggle("caret-down");
}
var toggler = document.getElementsByClassName("caret");
var i;

for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function itemClickHandler(evt) {
    toggleItem(evt.target);
  });
}


function isClosedGroup(el) {
  var isNestedGroup = el.classList.contains("nested");
  if (isNestedGroup) {
    var caret = el.previousElementSibling;
    var isOpenGroup = caret.classList.contains("caret-down");
    return !isOpenGroup;
  }
}
function showGroup(item) {
  var caret = item.parentNode.querySelector(".caret");
  toggleItem(caret);
}

var treeTop = document.querySelector("#myUL");
var visibleItems = treeTop.querySelectorAll(".visible");
visibleItems.forEach(function (item) {
  while (item !== treeTop) {
    if (isClosedGroup(item)) {
      showGroup(item);
    }
    item = item.parentNode;
  }
});

Hopefully this way of presenting the information makes it easier to work through, and understand what each part does.

When it comes to the active page, you’ll also want some code before this that sets one of the items to be visible.

To achieve that we want to get the page name:

function getPageName(pathname) {
  var regex = /(\w+).asp/;
  var match = regex.exec(pathname);
  return match[1];
}
...
var pageName = getPageName(location.pathname);

Each of the page items can have an id that matches the page name:

          <li>Black Tea</li>
          <li>White Tea</li>
          <li data-id="tryit">Try It</li>

And with that page name, we want to find that appropriate item with the same name:

function findMatchingItem(container, pagename){
  var items = container.querySelectorAll("li");
  return Array.from(items).find(function (item) {
    return item.dataset.id === pagename;
  });
}
...
var treeTop = document.querySelector("#myUL");
var pageName = getPageName(location.pathname);
var item = findMatchingItem(treeTop, pageName);
item.classList.add("visible");

The full code to use on the tryit page is as follows:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
ul, #myUL {
  list-style-type: none;
}

#myUL {
  margin: 0;
  padding: 0;
}

.caret {
  cursor: pointer;
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none;
}

.caret::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}

.caret-down::before {
  -ms-transform: rotate(90deg); /* IE 9 */
  -webkit-transform: rotate(90deg); /* Safari */'
  transform: rotate(90deg);  
}

.nested {
  display: none;
}

.active {
  display: block;
}
</style>
</head>
<body>

<h2>Tree View</h2>
<p>A tree view represents a hierarchical view of information, where each item can have a number of subitems.</p>
<p>Click on the arrow(s) to open or close the tree branches.</p>

<ul id="myUL">
  <li><span class="caret">Beverages</span>
    <ul class="nested">
      <li>Water</li>
      <li>Coffee</li>
      <li><span class="caret">Tea</span>
        <ul class="nested">
          <li>Black Tea</li>
          <li>White Tea</li>
          <li>tryit</li>
          <li><span class="caret">Green Tea</span>
            <ul class="nested">
              <li>Sencha</li>
              <li>Gyokuro</li>
              <li>Matcha</li>
              <li>Pi Lo Chun</li>
            </ul>
          </li>
        </ul>
      </li>  
    </ul>
  </li>
</ul>

<script>
function toggleItem(item) {
  item.parentElement.querySelector(".nested").classList.toggle("active");
    item.classList.toggle("caret-down");
}
var toggler = document.getElementsByClassName("caret");
var i;

for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function itemClickHandler(evt) {
    toggleItem(evt.target);
  });
}

function getPageName(pathname) {
  var regex = /(\w+).asp/;
  var match = regex.exec(pathname);
  return match[1];
}
function findMatchingItem(selector, text){
  const allItems = Array.from(document.querySelectorAll(selector));
  return allItems.find(el => el.textContent === text);
}

function isClosedGroup(el) {
  var isNestedGroup = el.classList.contains("nested");
  if (isNestedGroup) {
    var caret = el.previousElementSibling;
    var isOpenGroup = caret.classList.contains("caret-down");
    return !isOpenGroup;
  }
}
function showGroup(item) {
  var caret = item.parentNode.querySelector(".caret");
  toggleItem(caret);
}

var pageName = getPageName(location.pathname);
var item = findMatchingItem("#myUL li", pageName);
item.classList.add("visible");

var treeTop = document.querySelector("#myUL");
var pageName = getPageName(location.pathname);
var item = findMatchingItem(treeTop, pageName);
item.classList.add("visible");

var visibleItems = treeTop.querySelectorAll(".visible");
visibleItems.forEach(function (item) {
  while (item !== treeTop) {
    if (isClosedGroup(item)) {
      showGroup(item);
    }
    item = item.parentNode;
  }
});
</script>

</body>
</html>

When you say “Mark one or more items as being visible:”

          <li class="visible">White Tea</li>

Do you mean that on each page (e.g. White Tea) I have to add class=“visible” to the active item?
I’m using Tree View in MediaWiki and have it stored in a Template that I call on each page.
MediaWiki has a Common.js file that applies to all pages.
Is there code I can add to Template or Common.js or to each page that can override the Template and mark a given item as active (e.g. White Tea on the White Tea page)?

And when I give each and every li item in the tree view a data-id, Is it ok to have a space between multiple words? Or should I use an underscore, or omit space between words?

When I add data-id to all list items in MediaWiki Template, and replace W3Schools JS with your script for tryit page, my tree view expands completely and all the carets stay in closed position.

When I copy your code into page and upload to server, tree view does not expand automatically to tryit item in tree view, but can be expanded manually. However, tryit is not bolded as active. See - http://dstall.com/Test/TryIt/

That was to get the initial code developed. You’ll see in the second post I dealt with that by having the page location set one to be active instead.

That data-id needs to match the location that’s intended to activate that item.

Your test page has a different pagename structure from the tryit page.

Modify the following function so that an appropriate name is provided:

function getPageName(pathname) {
  var regex = /(\w+).asp/;
  var match = regex.exec(pathname);
  return match[1];
}

On your page I might try splitting by the slashes, filter them to remove empty results, and get the last one.

function getPageName(pathname) {
  var parts = location.pathname.split("/").filter(txt => txt);
  return parts.pop();
}

For a path of "/Test/TryIt/" that will give you "TryIt" which is what you should put in data-id

Please note that I know absolutely nothing about JS, I’m just trying to get this working.
I created complete heirarchy of folders in my public_html folder that correspond to LI in code below, and an index.html file in each folder.
I’m using same code below on every page, except class=“visible” is moved to whatever page is active.
NOTE: I realize that the links don’t work because of incorrect paths, but I’m NOT concerned about fixing that right now.
I must get this working under normal situation before I try to adapt it to MediaWiki.
And it still doesn’t work.
The tree view still remains collapsed on each and every page.
See - http://dstall.com/Beverages/
Change URL accordingly to see other pages.
Beverages
Water
Coffee
Tea
Black Tea
White Tea
Green Tea
Sencha
Gyokuro
Matcha
Pi Lo Chun

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
ul, #myUL {
  list-style-type: none;
}

#myUL {
  margin: 0;
  padding: 0;
}

.caret {
  cursor: pointer;
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none;
}

.caret::before {
  content: "\25B6";
  color: black;
  display: inline-block;
  margin-right: 6px;
}

.caret-down::before {
  -ms-transform: rotate(90deg); /* IE 9 */
  -webkit-transform: rotate(90deg); /* Safari */'
  transform: rotate(90deg);  
}

.nested {
  display: none;
}

.active {
  display: block;
}
</style>
</head>
<body>

<h2>Tree View</h2>
<p>A tree view represents a hierarchical view of information, where each item can have a number of subitems.</p>
<p>Click on the arrow(s) to open or close the tree branches.</p>

<ul id="myUL">
  <li data-id="beverages" class="visible"><span class="caret"><a href="dstall.com/Beverages/">Beverages</a></span>
    <ul class="nested">
      <li data-id="water"><a href="dstall.com/Beverages/Water/">Water</a></li>
      <li data-id="coffee"><a href="dstall.com/Beverages/Coffee/">Coffee</a></li>
      <li data-id="tea"><span class="caret"><a href="dstall.com/Beverages/Tea/">Tea</a></span>
        <ul class="nested">
          <li data-id="blacktea"><a href="dstall.com/Beverages/Tea/Black Tea/">Black Tea</a></li>
          <li data-id="whitetea"><a href="dstall.com/Beverages/Tea/White Tea/">White Tea</a></li>
          <li data-id="tryit"><a href="dstall.com/Beverages/Tea/Try It/">Try It</a></li>
          <li data-id="greentea"><span class="caret"><a href="dstall.com/Beverages/Tea/Green Tea/">Green Tea</a></span>
            <ul class="nested">
              <li data-id="sencha"><a href="dstall.com/Beverages/Green Tea/Sencha/">Sencha</a></li>
              <li data-id="gyokuro"><a href="dstall.com/Beverages/Green Tea/Gyokuro/">Gyokuro</a></li>
              <li data-id="matcha"><a href="dstall.com/Beverages/Green Tea/Matcha/">Matcha</a></li>
              <li data-id="pilochun"><a href="dstall.com/Beverages/Green Tea/Pi Lo Chun/">Pi Lo Chun</a></li>
            </ul>
          </li>
        </ul>
      </li>  
    </ul>
  </li>
</ul>

<script>
function toggleItem(item) {
  item.parentElement.querySelector(".nested").classList.toggle("active");
    item.classList.toggle("caret-down");
}
var toggler = document.getElementsByClassName("caret");
var i;

for (i = 0; i < toggler.length; i++) {
  toggler[i].addEventListener("click", function itemClickHandler(evt) {
    toggleItem(evt.target);
  });
}

function getPageName(pathname) {
  var regex = /(\w+).asp/;
  var match = regex.exec(pathname);
  return match[1];
}
function findMatchingItem(selector, text){
  const allItems = Array.from(document.querySelectorAll(selector));
  return allItems.find(el => el.textContent === text);
}

function isClosedGroup(el) {
  var isNestedGroup = el.classList.contains("nested");
  if (isNestedGroup) {
    var caret = el.previousElementSibling;
    var isOpenGroup = caret.classList.contains("caret-down");
    return !isOpenGroup;
  }
}
function showGroup(item) {
  var caret = item.parentNode.querySelector(".caret");
  toggleItem(caret);
}

var pageName = getPageName(location.pathname);
var item = findMatchingItem("#myUL li", pageName);
item.classList.add("visible");

var treeTop = document.querySelector("#myUL");
var pageName = getPageName(location.pathname);
var item = findMatchingItem(treeTop, pageName);
item.classList.add("visible");

var visibleItems = treeTop.querySelectorAll(".visible");
visibleItems.forEach(function (item) {
  while (item !== treeTop) {
    if (isClosedGroup(item)) {
      showGroup(item);
    }
    item = item.parentNode;
  }
});
</script>

</body>
</html>

I created the entire Beverages directory in my public_html folder, with folders for every item nested accordingly and index.html file in every folder.
Tree view still remains collapsed on every page.
See - http://dstall.com/Beverages/Tea/
Links only work from http://dstall.com/Beverages/ but that’s sufficient to see that tree view remains collapsed on every page.
I’m not going to fix links on each and every page at this point.
Here’s pertinent code that’s used on every page with data-id for every LI -

<ul id="myUL">
  <li data-id="beverages"><span class="caret"><a href="/Beverages/">Beverages</a></span>
    <ul class="nested">
      <li data-id="water"><a href="Water/">Water</a></li>
      <li data-id="coffee"><a href="Coffee/">Coffee</a></li>
      <li data-id="tea"><span class="caret"><a href="Tea/">Tea</a></span>
        <ul class="nested">
          <li data-id="blacktea"><a href="Tea/Black Tea/">Black Tea</a></li>
          <li data-id="whitetea"><a href="Tea/White Tea/">White Tea</a></li>
          <li data-id="tryit"><a href="Tea/Try It/">Try It</a></li>
          <li data-id="greentea"><span class="caret"><a href="Tea/Green Tea/">Green Tea</a></span>
            <ul class="nested">
              <li data-id="sencha"><a href="Tea/Green Tea/Sencha/">Sencha</a></li>
              <li data-id="gyokuro"><a href="Tea/Green Tea/Gyokuro/">Gyokuro</a></li>
              <li data-id="matcha"><a href="Tea/Green Tea/Matcha/">Matcha</a></li>
              <li data-id="pilochun"><a href="Tea/Green Tea/Pi Lo Chun/">Pi Lo Chun</a></li>
  

When the file path ends with /Tea/ then the data-id needs to be “Tea”.
When the file path ends with /tea/ then the data-id needs to be “tea”.

The capitalization matters.

Also, your page is still using the code that gets the asp page name, and hasn’t been updated to the updated code.

I changed all data-id to exact match with path (name of folder in directory) -
/Tea/ - http://dstall.com/Beverages/Tea/
Also updated code as you provided to -

function getPageName(pathname) {
  var parts = location.pathname.split("/").filter(txt => txt);
  return parts.pop();
}

Tree view is still not expanding to the active LI item.
For example, see - http://dstall.com/Beverages/Tea/

It looks like the findMatchingItems() function needs to be updated too, so that the data information is examined instead of the text.

function findMatchingItem(selector, text){
  const allItems = Array.from(document.querySelectorAll(selector));
  return allItems.find(el => el.dataset.id === text);
}

Still no cigar. Tree view remains completely collapsed on page load.

On the page I see the following excerpt:

var pageName = getPageName(location.pathname);
var item = findMatchingItem("#myUL li", pageName);
item.classList.add("visible");

var treeTop = document.querySelector("#myUL");
var pageName = getPageName(location.pathname);
var item = findMatchingItem(treeTop, pageName);
item.classList.add("visible");

Please remove the last three lines.

Getting closer.

Water, Coffee, and Tea in Beverages all work - tree view opens on each of those pages to reveal the subcategories (water, coffee, tea).

However, the active page is not bolded, and it’s URL link is not stripped from it.

JS does not work for any of the Tea subitems - Black Tea, White Tea, Try It, or Green Tea.

But does work for 1st 3 subitems in Green Tea - Sencha, Gyokuro, Matcha.
However, not for 4th Green Tea subitem Pi Lo Chun.

See — http://dstall.com/Beverages/

Welcome to programming, it’s solving issues a bit at a time until there are fewer issues remaining.

Bolding text is something that CSS takes care of, so that’s a good next step to take.

I’m a designer, not a programmer.
Without you I’m SOL.
I’ll work on the bold issue, but that will still leave the URL link.
W3Schools CSS and/or JS bolded text for active page and removed link.
Is your code missing that somehow?

With your URL links, because the links are in different directories from each other, you will need to use paths from the common root of the domain by starting the href link with a slash, and pathing down from there.

The White link for example should be:

<a href="/Beverages/Tea/White Tea/">White Tea</a>

I’m going to be using tree view in MediaWiki, and MW has it’s own markdown for linking to pages and works fine with W3Schools HTML.

So I’m more concerned about getting all the active pages to expand tree view to active page.
That’s not happening for Tea subitems - Black Tea, White Tea, Try It, or Green Tea, and Pi Lo Chun subitem of Green Tea.
Expansion works fine for Water, Coffee, and Tea, and Green Tea subitems - Sencha, Gyokuro, and Matcha.

Once that works, then I need to get name of active page in tree view to bold, and strip link from it.

RE: Link paths, when I change path for every LI item so it starts from Beverages directory, then subpages act like they’re the root.
For instance, on Beverages page, I show tree view and click on Tea subitem.
Then from Tea page, I click on Coffee, and URL shows as -
http://dstall.com/Beverages/Tea/Coffee
NOT http://dstall.com/Beverages/Coffee as it should.
So I end up in a big 404 loop.
I can only test page links from Beverages tree view.
But again, that’s of lesser import right now as long as MediaWiki links work with JS and CSS

The tea page is at https://dstall.com/Beverages/Tea/

The coffee link is <a href="Coffee/">Coffee</a>

That coffee link is going to try to access https://dstall.com/Beverages/Tea/Coffee/ because that’s how links work.

If you don’t want it to do that, then the menu link needs to be from the top-level root instead, such as "/Beverages/Coffee/"

Sorry. Thought I’d got them all changed. They’re all changed now and working. Thanks.

Any idea why tree view’s not expanding on Tea subitems - Black Tea, White Tea, Try It, or Green Tea, and Pi Lo Chun subitem of Green Tea?

That’s because that kind of requirement hasn’t been known about until now.

When the menu item that needs to be visible is being shown, the script also needs to check if that item has a nested section, so that the nested section can be made visible too.