What is the best way to change the class of the inline table of contents element using JavaScript?

INQUIRY

Hello, I am currently working on building a straightforward navigation site and I have a question regarding how to modify or add a class to one of the li elements within the inline table of contents (toc) on the right side. I want to style it using CSS.

This is my current page:

Link to My Page

I aim to achieve a similar effect as demonstrated here: Desired Page Behavior

(If you scroll up and down the main page, you will notice changes in the styling of the toc element on the right)

You can also observe the same behavior in this example: Example Page (Notice how the toc on the right changes as you scroll)

I tried analyzing the JavaScript used in these pages, but found it quite complex and challenging to decipher. So, I'm reaching out for any suggestions or insights.

Thank you,

Sachin

NOTE - QUERY RESOLVED AND CODE updated via the provided link based on responses below

CODE IMPLEMENTED

The CSS styles I've worked on:

.doc-outline ul li a {
  text-decoration: none;
}

.doc-outline ul li a:hover {
  text-decoration: underline;
}

.doc-outline li.selected {
  font-weight: 600;
  border-color: #0065b3;
}

.doc-outline a:visited {
  color: #0065b3;
}

Relevant sections of HTML code

   <div class="primary-holder column is-two-thirds-tablet is-three-quarters-desktop">
      <div class="columns has-large-gaps is-gapless-mobile">

        <!-- MAIN CONTENT -->
        <div id="main-column" class="column is-full is-four-fifths-desktop">
          <main id="main" class ="content" lang="eng-us">
            <h1 id="learn-conditional-logic-with-branch-and-loop-statements">Learn conditional logic with branch and loop statements</h1>.....etc.
          </main>   
        </div>

       <!-- TOC ON THE RIGHT SIDE -->
       <div class="right-sidebar column is-one-quarter is-one-fifth-desktop is-hidden-mobile is-hidden-tablet-only">
        <nav class="doc-outline is-fixed is-vertically-scrollable", id="affixed-right-sidebar">
        <nav id="side-doc-outline">
         <ul class="section-nav">
           <li class="toc-entry toc-h2"><a href="#make-decisions-using-the-if-statement">Make decisions using the if statement</a></li>
           <li class="toc-entry toc-h2"><a href="#make-if-and-else-work-together">Make if and else work together</a></li>
           <li class="toc-entry toc-h2"><a href="#use-loops-to-repeat-operations">Use loops to repeat operations</a></li>
           <li class="toc-entry toc-h2"><a href="#work-with-the-for-loop">Work with the for loop</a></li>
           <li class="toc-entry toc-h2"><a href="#created-nested-loops">Created nested loops</a>
           <ul>
            <li class="toc-entry toc-h3"><a href="#test">Test</a></li>
          </ul>
          </li>
          <li class="toc-entry toc-h2"><a href="#combine-branches-and-loops">Combine branches and loops</a></li>
          </ul>
       </nav>
      </nav>
     </div>

    </div>
   </div>   

JavaScript Functionality

Modified JS code utilized through the provided link following feedback - Potential for simplifying some code blocks...

Highlighting Correct Menu Item in RHS TOC

(function rhsToc() {
// Initialize global variables outside functions
let observer = new IntersectionObserver(handler, { threshold: [0] });
let selection;
let headings = [...document.querySelectorAll("#main h2, #main h3")];
let rhsToc = [...document.querySelectorAll("ul.section-nav a")];
let a = null;
let lastScroll = 0;
let headingMenuMap = headings.reduce((acc, h) => {
    let id = h.id;
    acc[id] = rhsToc.find(a => a.getAttribute("href") === "#" + id);
    return acc;
  }, {})

headings.forEach(elem => observer.observe(elem));

// Detect Scroll Direction
scrollDetect();
let scrollDirection = [];
function scrollDetect(){
    var lastScroll = 0;
    window.onscroll = function() {
        let currentScroll = document.documentElement.scrollTop || document.body.scrollTop; // Get Current Scroll Value
        if (currentScroll > 0 && lastScroll <= currentScroll){
          lastScroll = currentScroll;
          scrollDirection = "down";
        }else{
          lastScroll = currentScroll;
          scrollDirection = "up"
        }
    };
  }

function handler(entries) {

    // Update selection with current entries.
    selection = (selection || entries).map( s => entries.find(e => e.target.id === s.target.id) || s);

    // Keep only true values
    filteredArr = selection.filter(x => x.isIntersecting == true );

    // Find last visible/intersecting (use a copied array for that, since reverse is an in place method)
    let firstVisibleId = [...selection].find(x => x.isIntersecting) ? [...selection].find(x => x.isIntersecting).target.id : null;

    // Is a firstVisibleId returned? If not, then follow the below steps
    if (firstVisibleId === null){
        // Were you scrolling down? - Then do nothing
        if (scrollDirection == "down"){
            // Do nothing!
        } else {
            // Scrolling up - Remove 'selected' from current menu item and add it to the menu item above it
            const current = document.querySelector(`#side-doc-outline > ul li.selected`);
            if (current) {
                current.classList.remove('selected');
            }
            // If there is no previous sibling with a class of 'toc-entry', you're at the top of the branch, so move up a level unless you reach section-nav
            if(previousByClass(a.parentElement, "toc-entry") == null){
                parent_by_selector(a.parentElement, "ul:not(.section-nav)") ? parent_by_selector(a.parentElement, "ul:not(.section-nav)").parentElement.classList.add("selected") : null;
            } else {
                previousByClass(a.parentElement, "toc-entry") ? previousByClass(a.parentElement, "toc-entry").classList.add("selected") : null;
            }
        }
        return;
    }

    // Otherwise, remove 'selected' from the active item in the RHS toc
    const current = document.querySelector(`#side-doc-outline > ul li.selected`);
    if (current) {
        current.classList.remove('selected');
    }

    // Add 'selected' to the target item in the RHS toc
    for (s of selection) {
        let targetId = s.target.id;
        // Get the entry from the generated map.
        a = headingMenuMap[targetId];
        if (firstVisibleId === targetId) {
            a.parentElement.classList.add("selected");
            return;
        } 
    };
}

})();

// Ensure Functions Initiate After Document Loads
document.addEventListener('DOMContentLoaded', tree);

Answer №1

I've encapsulated the text within each section with a class called paragraph, assigned for observation, and included a class named content to its content - although it is not currently utilized, I still recommend doing so.

UPDATE: incorporated your suggestion on how to choose the element in case of multiple overlaps.

Check this link for more details

let observer = new IntersectionObserver(handler, {
  threshold: [0.2]
});
let selection;
let paragraphs = [...document.querySelectorAll("#main .paragraph")];
let submenu = [...document.querySelectorAll("ul.section-nav a")];
let paragraphMenuMap = paragraphs.reduce((acc, p) => {
  let id = p.firstElementChild.id;
  acc[id] = submenu.find(a => a.getAttribute("href") === "#" + id);
  return acc;
}, {})

paragraphs.forEach(elem => observer.observe(elem));

function handler(entries) {

  // Update selection with current entries.
  selection = (selection || entries).map(
    s => entries.find(e => e.target.firstElementChild.id === s.target.firstElementChild.id) || s
  );

  // Find last visible/intersecting (use a copied array for that, since reverse is an in place method)
  let lastVisibleId = [...selection].reverse().find(x => x.isIntersecting).target.firstElementChild.id;

  for (s of selection) {
    let targetId = s.target.firstElementChild.id;
    // avoid searching the dom and just get the entry from our map.
    let a = paragraphMenuMap[targetId];

    if (lastVisibleId === targetId) {
      let parentElem = a;
      // ensure that parent menu entries are selected too
      while (parentElem = parentElem.parentElement.closest(".toc-entry")) {
        parentElem.classList.add("selected");
      }
    } else {
      a.parentElement.classList.remove("selected");
    }
  }
};

Answer №2

So from what I gather, you're looking to apply styles by adding classes rather than directly styling elements?

document.getElementsByTagName("li").addEventListener("click",()=>{
 this.style = "color:red";
})

If that's the case, using ClassList is the way to go:

document.getElementById("myDIV").classList.add("mystyle");

This adds the specified class, while remove() and toggle() take care of removing or toggling it.

To individually target elements, consider assigning IDs or classes and using them as indexes:

 `document.body.scrollTop;`

Then apply classes based on specific conditions:

 if(document.body.scrollTop == 100px){
     document.getElementById("myDIV").classList.add("mystyle");
 }

For a more dynamic approach, you can remove previous classes before adding new ones:

if(document.body.scrollTop == 100px){
    for(let i = 0;i<classOfLis.length,i++){
        classOfLis[i].classList.remove("selected");
    }
    document.getElementById("myDIV").classList.add("mystyle");
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unlocking the Power of React: Leveraging Component Functions Through Click Events on vis.js Edges

I'm working on a React component that looks like this: export default class App extends Component { // ... _openDialog = () => { this.setState({ isDialogOpen: true }); }; render() { return ( <div> <div clas ...

Enhance your website with a stylish CSS jQuery menu that lets you toggle, slide

If you're curious about the code, take a look here! Here are some of the issues I'm facing: The div doesn't toggle to hide when clicked on itself, only when another one is clicked. (UPDATE: it actually won't toggle at all) When t ...

How to set a default class for Bootstrap tabs using AngularJS

I'm working with an angular tabbable element and I need to set a default tab as well as add a specific class to one of the tabs. For example, I want tab 2 to be selected by default and give tab 1 a specific class. Here is the link to the code: http:/ ...

Activating the Submission of a File in a JQuery Form Plugin

Currently, I am using the JQuery Form Plugin to facilitate file uploads on my website. Instead of using a standard submit button, I would prefer to have a div element trigger the submission process. Here is the code I have attempted: $(document).on(&apo ...

Utilize the precise Kendo chart library files rather than relying on the kendo.all.min.js file

My application currently uses the Kendo chart, and for this purpose, it utilizes the "kendo.all.min.js" file which is quite large at 2.5 MB. To optimize the speed performance of the application, I decided to only include specific Kendo chart libraries. In ...

Managing the size of personalized shapes in Three.js

I need help with a custom object that represents a frame created by subtracting two meshes. generateFrame (width, height, depth) { const frameMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1)); frameMesh.scale.set(width, height, depth); c ...

Send data using Javascript without having to refresh the page

As I work on submitting a form in JavaScript, the AJAX feature is functioning perfectly when manually submitting the form. However, there seems to be an issue when JavaScript attempts to submit it, resulting in a refresh. The current code snippet I am wor ...

Background image on webpage failed to load

I've encountered an issue with a background image I downloaded for my website - it simply will not display. Here is the CSS code I am using: background-image: url(Resources/Icons/background-image.jpg); background-attachment: fixed; Even after tryin ...

Scrolling to the complete height of a div using scrollTop

Experience an automated scroll to the bottom of the div, followed by a smooth upward motion over 5 seconds, all while looping seamlessly. A small issue arises where the code only recognizes the standard height set for the div, not its actual scrollable he ...

What's the best way to incorporate a clear options icon when choosing an option in a MaterialUI select component?

I am looking to enhance the user experience by adding a clear options icon that appears when a selection is made from the list of options. <Select InputProps={{ startAdornment: ( <InputAdornment position='end'> <Cl ...

In JavaScript coding language, you can use this syntax to create an array and push the

When the variable foo is defined, why does the following code behave in a certain way? var array = [].push(foo); When executed, why does the output equal 1? Based on my testing, the variable array simply returns the length of the array. Therefore, if ...

Getting an error message like "npm ERR! code ENOTFOUND" when trying to install Angular CLI using the command "

Currently, I am eager to learn Angular and have already installed Node version 18.13.0. However, when attempting to install Angular CLI using the command npm install -g @angular/cli, I encountered an issue: npm ERR! code ENOTFOUND' 'npm ERR! sys ...

Experimenting with Angular using a template that includes a mat-toolbar

Currently, I am in the process of writing tests for my Angular application. Here is a snippet of the template used in my AppComponent: <mat-toolbar color="primary"> <span>CRUD Exercise</span> <span class="example-spa ...

What could be causing the issue with retrieving HTTP requests in Nest.js even after configuring the controller?

After the individual who departed from the company utilized Nest.js to create this server-side system. They established the auth.controller, auth.service, auth.module, auth-token, jwt.strategy, and jwt-payload, with everything properly configured. I verifi ...

How can I style the outermost div of a jQuery UI dialog window using CSS?

I currently utilize jquery-ui for the creation of a dialog window, with the designated div to be displayed having the class 'dialog'. First, let's observe the HTML structure : <div class="click_me"> Click Me </div><!-- end o ...

`Failure to prompt an error following an unsuccessful post request in a node.js application using axios and express`

I'm currently facing an issue while trying to implement password change validation. The problem lies in not receiving the errorMessage from the server in case of an error. Although I've successfully managed to update the password and send back a ...

Utilize CSS to display line breaks within a browser output

If I wrap text in a <pre> element, line breaks will be displayed in the browser without needing to use <br/> tags. Can this same effect be achieved by utilizing CSS with a <div> element? ...

Fixing a CSS animation glitch when using JavaScript

I'm facing an unusual issue with my CSS/HTML Check out my code below: a:hover { color: deeppink; transition: all 0.2s ease-out } .logo { height: 300px; margin-top: -100px; transition: all 0.2s ease-in; transform: scale(1) } .logo:hover { transit ...

Delay execution of Selenium WebDriver until the element appears on the screen

After scouring Google and the SO site, I was able to find answers for JAVA but not for node.js. I have a web application that has a slow loading time. I want the selenium program to wait until the page is fully loaded before taking any actions. Below is ...

Implementing Vue.js - Applying a Class on Click Behavior

Is there a way in Vue.js to dynamically toggle a class on a click event for a div without relying on properties or data? Take a look at this div: <div class="quiz-item" @click="checkAnswer(1)"> Upon clicking this div, I want to add either the clas ...