Search code examples
javascriptdynamically-generated

generate string path out of a click sequence


I generate nested div elements based on an object structure. With a click on the parent you can toggle the children.

Now I want to generate a path, separated with slashes, of the click sequence and the "selected" elements. When the user clicks on read -> news -> sport the string path should be "read/news/sport". When the user now clicks on read -> books the path should be now "read/books"

Here is my current version: https://codepen.io/iamrbn/pen/yEqPjG

let path = "";

let object = {
  "design": {
    "inspiration": {},
    "news": {}
  },
  "read": {
    "news": {
      "sport": {}
    },
    "books": {}
  },
	"code": {}
}

let categoryContainer = document.querySelector(".categoryContainer")

function categoryTree(obj, parent, start = true) {
  for (var key in obj) {
    let div = document.createElement("div");
    div.textContent = key;
	 div.classList.add("category");
    if (parent.children) parent.className += " bold";
    if (!start) div.className = "normal hide category";

    div.addEventListener('click', function(e) {
      e.stopPropagation()
		this.classList.toggle('active');
      Array.from(div.children).forEach(child => {
        child.classList.toggle('hide');
      })
    })
    categoryTree(obj[key], div, false)
    parent.appendChild(div)
  }
}


categoryTree(object, categoryContainer)
.category {
	color: black;
	display: block;
	line-height: 40px;
	background-color: RGBA(83, 86, 90, 0.2);
	margin: 8px;
}

.category .category {
	display: inline-block;
	margin: 0 8px;
	padding: 0 8px;
}

.category.hide {display: none;}
.category.normal {font-weight: normal;}
.category.bold {font-weight: bold;}
.category.active {color: red;}
<div class="categoryContainer"></div>


Solution

  • Here's one approach. Your existing code is unmodified except for adding a call to the new getParents() function, which works by crawling up the DOM tree recursively to generate the "path" to the clicked node:

    let path = "";
    
    let object = {
      "design": {
        "inspiration": {},
        "news": {}
      },
      "read": {
        "news": {
          "sport": {}
        },
        "books": {}
      },
      "code": {}
    }
    
    let categoryContainer = document.querySelector(".categoryContainer")
    
    function categoryTree(obj, parent, start = true) {
      for (var key in obj) {
        let div = document.createElement("div");
        div.textContent = key;
        div.classList.add("category");
        if (parent.children) parent.className += " bold";
        if (!start) div.className = "normal hide category";
    
        div.addEventListener('click', function(e) {
          e.stopPropagation()
          this.classList.toggle('active');
          Array.from(div.children).forEach(child => {
            child.classList.toggle('hide');
          })
          var thePath = getParents(e.target); // <--- new
          console.log(thePath)
        })
        categoryTree(obj[key], div, false)
        parent.appendChild(div)
      }
    }
    
    function getParents(node, path) {
      // Cheat a bit here: we know the textnode we want is the first child node, so we don't have to iterate through all children and check their nodeType:
      let thisName = node.childNodes[0].textContent;
      path = path ? (thisName + "/" + path) : thisName ; 
      // iterate to parent unless we're at the container:
      if (node.parentNode.className.split(/\s+/).indexOf("categoryContainer") !== -1) {
        return path;
      } else {
        return getParents(node.parentNode, path);
      }
    }
    
    categoryTree(object, categoryContainer)
    .category {
      color: black;
      display: block;
      line-height: 40px;
      background-color: RGBA(83, 86, 90, 0.2);
      margin: 8px;
    }
    
    .category .category {
      display: inline-block;
      margin: 0 8px;
      padding: 0 8px;
    }
    
    .category.hide {
      display: none;
    }
    
    .category.normal {
      font-weight: normal;
    }
    
    .category.bold {
      font-weight: bold;
    }
    
    .category.active {
      color: red;
    }
    <div class="categoryContainer"></div>