skip to Main Content

i am trying to create a simple function where when a button is clicked it toggles a class and changes a data attribute from true to false.

here is what i have so far;

const hamburger = document.querySelector(".hamburger")
const navigation = document.querySelector(".nav-wrapper")
const visibility = navigation.getAttribute("data-visible")


hamburger.addEventListener('click', () => {

      if (visibility === 'false') {
            navigation.classList.toggle('active');
            navigation.setAttribute('data-visible', true)
      } else if (visibility === 'true') {
            navigation.classList.toggle('active');
            navigation.setAttribute('data-visible', false)
      }

})

the code toggles the class and changes the initial value of the data attribute from false to true but doesn’t change it back to false when clicked on again. would really appreciate some help on where i’ve gone wrong.

3

Answers


  1. It’s not quite clear what you’re trying to do (no html in your question). Using both a class .active and [data-visible] seems redundant. What is visibility in your handler function?

    Anyway, let’s simplify things. Here is a minimal reproducable example to play with. It uses event delegation to be able to handle all events in on function. The comments in the example try to clarify things.

    document.addEventListener(`click`, handle);
    
    function handle(evt) {
      if (evt.target.dataset?.visible) {
        // ▲ Yes, the click originated from the button!
        const nav = document.querySelector(`.navigation`);
        // ▲ toggle div.navigation class
        nav.classList.toggle(`active`);
        // now set the data-attribute (using data-set) to 0 or 1
        // using a conversersion to Number from the return value of 
        // nav.classList.contains (true or false), with the + operator ▼
        return evt.target.dataset.visible = +(nav.classList.contains(`active`));
      }
      // ... more handling here when needed ... //
      return true;
    }
    [data-visible="0"]:after {
      content: 'show navigation';
    }
    
    [data-visible="1"]:after {
      content: 'hide navigation';
    }
    
    .navigation {
      display: none;
    }
    
    .navigation.active {
      display: block;
    }
    <div class="hamburger">
      <!-- ▼ the button text is set in css -->
      <button data-visible="0"></button>
    </div>
    
    <div class="navigation">
      Navigation!
    </div>
    Login or Signup to reply.
  2. One main issue is you are declaring the visibility variable with the data value but never getting that value again.

    My answer simplifies your code.

    First, I use dataset instead of set/get Attribute. I set the value of visible based on of the opposite of the string version of visible is true. So if visible is true, it becomes false and vice versa. Also since you are using toggle, you can just apply that always, it doesn’t need to be in if/else if.

    Also depending on your needs, you don’t even have to use the data-visible attribute. You could always use navigation.classList.contains("active") too.

    const hamburger = document.querySelector(".hamburger")
    const navigation = document.querySelector(".nav-wrapper")
    
    hamburger.addEventListener('click', () => {
        navigation.dataset.visible = !(navigation.dataset.visible == "true");
        navigation.classList.toggle('active');
    })
    .nav-wrapper{display:none}
    
    .nav-wrapper.active{
      display:block
    }
    <button class="hamburger">Hamburger!</button>
    <div data-visible="true" class="nav-wrapper">
      WRAP IT!
    </div>
    Login or Signup to reply.
  3. There are many ways to toggle attributes and there are different ways to toggle other aspects that when applied will garner the same results. In the example below there are 4 dropdown menus with hamburger buttons.

    A. Toggle a className using .classList.toggle()

    B. Toggle a data-* attribute with delete and .dataset

    C. Toggle the boolean attribute [open] with .toggleAttribute()

    D. Toggle the styles directly using a checkbox and CSS (no JavaScript)

    Details are commented in example

    // Register "click" event to "button.burger.A"
    document.querySelector(".A").onclick = switchA;
    // Event handler toggles ".on" class on ".A"
    function switchA(e) {
      this.classList.toggle("on");
    }
    // Register "click" event to "button.burger.B"
    document.querySelector(".B").onclick = switchB;
    /**
     * Event handler will delete [data-on] attribute from ".B" if it's present,
     * otherwise it will add [data-on="."]
     * The value is arbitrary and the only requirement is that it must be
     * a String of a length of at least 1 character.
     */
    function switchB(e) {
      if (this.dataset.on) {
        delete this.dataset.on;
      } else {
        this.dataset.on = ".";
      }
    }
    // Register "click" event to "button.burger.C"
    document.querySelector(".C").onclick = switchD;
    // Event handler toggles the [open] attribute to/from true/false.
    function switchD(e) {
      this.toggleAttribute("open");
    }
    :root {
      font: 2ch/1.15 "Segoe UI";
    }
    
    main {
      list-style: upper-latin;
      margin-left: 25px;
    }
    
    h1 {
      font-size: 1.5rem;
      margin-bottom: 12px;
    }
    
    section {
      display: list-item;
    }
    
    section::marker {
      font-weight: 900;
      font-size: 1.15rem;
    }
    
    h2 {
      font-size: 1.25rem;
      margin-bottom: -18px;
    }
    
    code {
      padding: 5px 8px;
      font-family: Consolas;
      color: #930;
      background: #ddd
    }
    
    nav {
      width: 90vw;
      background: #d9d9d9;
      margin: 40px auto;
    }
    
    #D {
      display: none;
    }
    
    .burger {
      display: inline-block;
      margin-left: 80%;
      padding: 15px;
      border: 0;
      font: inherit;
      color: inherit;
      background: transparent;
      transition: opacity 0.15s linear;
      overflow: visible;
      cursor: pointer;
    }
    
    .burger:hover {
      opacity: 0.7;
    }
    
    .on .core,
    .on .core::before,
    .on .core::after,
    [data-on] .core,
    [data-on] .core::before,
    [data-on] .core::after,
    [open] .core,
    [open] .core::before,
    [open] .core::after,
    #D:checked+.D .core,
    #D:checked+.D .core::before,
    #D:checked+.D .core::after {
      background: #000;
    }
    
    .box {
      display: inline-block;
      position: relative;
      width: 40px;
      height: 24px;
    }
    
    .core {
      display: block;
      top: 50%;
      margin-top: -2px;
    }
    
    .core,
    .core::before,
    .core::after {
      position: absolute;
      width: 40px;
      height: 4px;
      border-radius: 4px;
      background: #000;
      transition: transform 0.15s ease;
    }
    
    .core::before,
    .core::after {
      content: "";
      display: block;
    }
    
    .core::before {
      top: -10px;
    }
    
    .core::after {
      bottom: -10px;
    }
    
    .burger .core {
      top: 2px;
    }
    
    .burger .core::before {
      top: 10px;
      transition: transform 0.15s ease, opacity 0.15s ease;
    }
    
    .burger .core::after {
      top: 20px;
    }
    
    .on .core,
    [data-on] .core,
    [open] .core,
    #D:checked+.D .core {
      transform: translate3d(0, 10px, 0) rotate(45deg);
    }
    
    .on .core::before,
    [data-on] .core::before,
    [open] .core::before,
    #D:checked+.D .core::before {
      transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
      opacity: 0.0;
    }
    
    .on .core::after,
    [data-on] .core::after,
    [open] .core::after,
    #D:checked+.D .core::after {
      transform: translate3d(0, -20px, 0) rotate(-90deg);
    }
    
    .drop {
      list-style: none;
      width: 100%;
      max-height: 0;
      margin-top: -5px;
      margin-left: -40px;
      text-align: center;
      overflow: hidden;
      transition: 1s ease;
    }
    
    .drop li {
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 12px;
    }
    
    .drop li:last-of-type {
      padding-bottom: 36px;
    }
    
    .drop li a {
      display: block;
      width: 100%;
      font-variant: small-caps;
      text-decoration: none;
      color: #2d2f31;
    }
    
    .on+.drop,
    [data-on]+.drop,
    [open]+.drop,
    #D:checked+.D+.drop {
      max-height: 999px;
    }
    <main>
      <h1>Switching States</h1>
      <section>
        <h2><code>.classList.toggle("on")</code></h2>
        <nav>
          <button class="burger A">
            <b class="box">
              <b class="core"></b>
            </b>
          </button>
          <menu class="drop">
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
          </menu>
        </nav>
      </section>
      <section>
        <h2><code>.dataset.on</code></h2>
        <nav>
          <button class="burger B">
            <b class="box">
              <b class="core"></b>
            </b>
          </button>
          <menu class="drop">
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
          </menu>
        </nav>
      </section>
      <section>
        <h2><code>.toggleAttribute("open")</code></h2>
        <nav>
          <button class="burger C">
            <b class="box">
              <b class="core"></b>
            </b>
          </button>
          <menu class="drop">
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
          </menu>
        </nav>
      </section>
      <section>
        <h2><code>&lt;input type="checkbox"&gt;</code></h2>
        <nav>
          <!--
            checkbox and label are accossiated with each other
            by 
            input#id and label[for] attributes being assigned the same
            value ("D" in this example). When such an accociation 
            exists, a click on the label will be as if the checkbox 
            was clicked as well.
          -->
          <input id="D" type="checkbox" hidden>
          <label for="D" class="burger D">
            <b class="box">
              <b class="core"></b>
            </b>
          </label>
          <menu class="drop">
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
            <li><a href="#">Link</a></li>
          </menu>
        </nav>
      </section>
    </main>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search