Where to Discuss?

Local Group

Preface

Goal: Toggle layout apperance with plain javascript.

This is more like a javacript tutorial, which use a bunch of Bootstrap class.


1: General Preview

This below is general preview of, what javascript toggler page design that we want to achieve.

Bulma SASS: General Preview of Javascript Toggler Design

Source image is available in inkscape SVG format, so you can modify, and make your own preview quickly.


2: Toggler Component

Sidebar design is siginficantly cool for short article, but for long article this would make your appearance looks unbalanced. Sidebar is necessary, but it takes space on your screen. Talking about space real estate, sometimes we need to be more flexible, while reading an article in a blog. This means we can let user to decide how our layout arranged, by in the same time we provide default layout.

Instead of changing the whole design layout alltogether, this article show how we can achieve this without too much effort.

Two Buttons

Our toggler component consist of two buttons:

  • Left Button: Release maxwidth class. Applied for tablet screen or beyond.

  • Right Button: Hide Sidebar. Applied for tablet screen or beyond.

HTML Content

Our toggler component is simply a HTML document, with a bunch Bootstrap class, and two awesome icons.

  <section class="m-1 d-none d-sm-block">
    <button class="btn btn-sm btn-light
                   float-left oc-black-text">
        <img src="assets/icons/chevrons-left.svg"
             alt="Toggle Maxwidth">

    </button>
    <button class="btn btn-sm btn-light
                   float-right oc-black-text">
        <img src="assets/icons/chevrons-right.svg"
             alt="Toggle Sidebar">

    </button>
    <div class="clearfix"></div>
  </section>

It is easier for me to use SVG provided in feathericons as image source.

Bootstrap OC: Toggler Component

This is just an example. Your are free working with your own design.

Tablet Only

Since toggler is set applied for tablet screen, we can hide the toggler component in mobile screen.

  <section class="m-1 d-none d-sm-block">
    ...
  </section>

Button ID

We need to give each button an ID, so that javascript can locate the chosen element. We also need to give a class to detect its state.

Left Button: left_toggler ID with left_toggler_active class.

    <button id="left_toggler"
            class="btn btn-sm btn-light
                   float-left oc-black-text
                   left_toggler_active">
        ...
    </button>

Right Button: right_toggler ID with right_toggler_active class.

    <button id="right_toggler"
            class="btn btn-sm btn-light
                   float-right oc-black-text
                   right_toggler_active">
        ...
    </button>

Javacript.

Our first javascript example should be simple. This script will handle onclick event on both element, in an unobstrusive fashioned.

document.addEventListener("DOMContentLoaded", function(event) { 
  const leftToggler  = document.getElementById("left_toggler");
  const rightToggler = document.getElementById("right_toggler");

  leftToggler.onclick = function() {
    leftToggler.classList.toggle("left_toggler_active");
    return false;
  }

  rightToggler.onclick = function() {
    rightToggler.classList.toggle("right_toggler_active");
    return false;
  }
});

The problem with custom scripting for specific design is, the script should reflect the html tag from the design. Thus script cannot be portable.

To port for your own design, you need to change with a bit of adjustment.

Inspect Element

You can load the page, click each button, and check whether, the active class is toggled.

Bootstrap OC: Inspect Toggle Class


3: Toggling Icon

Visual First

Both toggle icon should reflect the state, whether active or inactive. We can utilize different icon, flip the each arrow.

Four State

Now we have four states.

  • Left Right:

Bootstrap OC:Toggle: Left Right

  • Left Left:

Bootstrap OC:Toggle: Left Left

  • Right Left:

Bootstrap OC:Toggle: Right Left

  • Right Right:

Bootstrap OC:Toggle: Right Right

Main Script

Consider add a few lines to handle onclick event.

  const leftToggler  = document.getElementById("left_toggler");
  const rightToggler = document.getElementById("right_toggler");

  leftToggler.onclick = function() {
    leftToggler.classList.toggle("left_toggler_active"); 

    toggleLeftIcon();
    leftToggler.blur();

    return false;
  }

  rightToggler.onclick = function() {
    rightToggler.classList.toggle("right_toggler_active");

    toggleRightIcon();
    rightToggler.blur();

    return false;
  }

At the end of each script, we are going to blur the focus, so that the clicked element will get clean looks, instead of a bordered icon.

Left Icon

This will flip AwesomeIcon from chevrons-left.svg to chevrons-right.svg, and flip back on the next click.

  function toggleLeftIcon() {
    const isActiveLeft = leftToggler.classList
      .contains("left_toggler_active");
   
    const leftIcon = leftToggler
      .getElementsByTagName("img")[0];

    if (isActiveLeft) {
      leftIcon.src = "assets/icons/chevrons-left.svg";
      console.log("left toggler class is active");
    } else {
      leftIcon.src = "assets/icons/chevrons-right.svg"
      console.log("left toggler class is inactive");
    }
  }

Right Icon

On the opposite icon, this will flip AwesomeIcon from chevrons-right.svg to chevrons-left.svg, and flip back on the next click.

  function toggleRightIcon() {
    const isActiveRight = rightToggler.classList
      .contains("right_toggler_active");

    const rightIcon = rightToggler
      .getElementsByTagName("img")[0];
  
    if (isActiveRight) {
      rightIcon.src = "assets/icons/chevrons-right.svg"      
      console.log("right toggler class is active");
    } else {
      rightIcon.src = "assets/icons/chevrons-left.svg"
      console.log("right toggler class is inactive");
    }
  }

How does it works?

These functions rely on checking each state, by using isActiveLeft or isActiveRight.

Left Icon:

  function toggleLeftIcon() {
    const isActiveLeft = leftToggler.classList
      .contains("left_toggler_active");
    ...
    
    if (isActiveLeft) {
      ...
    } else {
      ...
    }
  }

Right Icon:

  function toggleRightIcon() {
    const isActiveRight = rightToggler.classList
      .contains("right_toggler_active");
    ...
  
    if (isActiveRight) {
      ...
    } else {
      ...
    }
  }

Inspect Element

You can load the page, click each button, and check the console tab.

Bootstrap OC: Toggle Console Log


4: Toggling Layout Appearance

This should be functional.

From icon appearance, we go further to explore, what each clicks does. Ecah click would change the layout documents using Bootstrap class.

HTML Content

Which Layout?

We need to alter a bit our layout, give the respective element a marker class.

Maxwidth: maxwidth_toggler

  <!-- header -->
  <nav class="navbar navbar-expand-md navbar-light fixed-top
              maxwidth maxwidth_toggler
              oc-white z-depth-3 hoverable">
    ...
  </nav>

  <!-- responsive multicolor main layout -->
  <div class="row layout-base
              maxwidth maxwidth_toggler">
    ...
  </div>

  <!-- footer -->
  <footer class="footer">
    <div class="text-dark text-center
                maxwidth maxwidth_toggler
                oc-white z-depth-3 hoverable">
      ...
    </div>
  </footer>

Sidebar: main_toggler, main_toggler_wrapper, and aside_toggler.

    <main id="main_toggler"
          class="col-md-8 px-0">
      <section id="main_toggler_wrapper"
               class="main-wrapper oc-blue-5">
        ...
      </section>
    </main>

    <aside id="aside_toggler"
           class="col-md-4 px-0">   
      ...
    </aside>

Main Script

Again, add more lines to handle onclick event.

  const leftToggler  = document.getElementById("left_toggler");
  const rightToggler = document.getElementById("right_toggler");

  leftToggler.onclick = function() {
    leftToggler.classList.toggle("left_toggler_active");
 
    toggleLeftIcon();
    toggleLeftLayout(); 

    leftToggler.blur();
    return false;
  }

  rightToggler.onclick = function() {
    rightToggler.classList.toggle("right_toggler_active");

    toggleRightIcon();
    toggleRightLayout();

    rightToggler.blur();
    return false;
  }

At the end of each script, we are going to blur the focus, so that the clicked element will get clean looks, instead of a bordered icon.

Left Layout

Disable maxwidth class.

This function will enable or disable custom made maxwidth class, for use with wide screen

  function toggleLeftLayout() {
    const maxWidthTogglers = document
      .getElementsByClassName("maxwidth_toggler");

    // ECMAScript 2015 
    for (let mwt of maxWidthTogglers) {
      mwt.classList.toggle("maxwidth");
    }
  }

I’m using ECMAScript 2015 for loop example. You can use the old school while loop, for compatibility with old browser.

    const maxWidthTogglers = document
      .getElementsByClassName("maxwidth_toggler);

    var i=0;
    while (i < maxWidthTogglers.length) {
      maxWidthResizers[i].classList.toggle("maxwidth");
      i++;
    }

From the normal view as this below:

Bootstrap OC: Left Button: Wide Normal

To a wider view as this below, after clicking the left button (disable maxwidth).

Bootstrap OC: Left Button: Wide Active

And even wider content, after clicking the right button (hiding sidebar), as we wil see in the next on click event handler.

Bootstrap OC: Both Button: Wide

Right Layout

This is our toggle sidebar wit two task.

  • Hide sidebar with d-none d-sm-block d-md-none class.

  • Change width of main content from col-md-8 to col-md-12 class. And add custom single class to fix margin issue.

  function toggleRightLayout() {
    const isActiveRight = rightToggler.classList
      .contains("right_toggler_active");

    const mainToggler  = document.getElementById("main_toggler");
    const mainTogglerW = document.getElementById("main_toggler_wrapper");
    const asideToggler = document.getElementById("aside_toggler");

    if (isActiveRight) {
      mainToggler.classList.add("col-md-8");
      mainToggler.classList.remove("col-md-12");

      mainTogglerW.classList.remove("single");

      asideToggler.classList.remove("d-none"); 
      asideToggler.classList.remove("d-sm-block");
      asideToggler.classList.remove("d-md-none");
    } else {
      mainToggler.classList.add("col-md-12");
      mainToggler.classList.remove("col-md-8");

      mainTogglerW.classList.add("single");

      asideToggler.classList.add("d-none");
      asideToggler.classList.add("d-sm-block");
      asideToggler.classList.add("d-md-none");
    }
  }

Now from the normal view as below:

Bootstrap OC: Right Button: Normal

You can switch to this:

Bootstrap OC: Gap Issue

I usually enjoy wider view while reading on the internet.


5: Fix Edge and Gap in Layout

Just in case you didn’t notice, my design has a flaw with code above. The edge on the left edge is lost.

It can be fix as below:

Main Script

Again, add more lines to handle onclick event.

  const leftToggler  = document.getElementById("left_toggler");
  const rightToggler = document.getElementById("right_toggler");

  leftToggler.addEventListener("click", () => {
    leftToggler.classList.toggle("left_toggler_active");
 
    toggleLeftIcon();
    toggleLeftLayout(); 
    fixGapAndEdge();

    leftToggler.blur();
    return false;
  });

  rightToggler.addEventListener("click", () => {
    rightToggler.classList.toggle("right_toggler_active");

    toggleRightIcon();
    toggleRightLayout();
    fixGapAndEdge();

    rightToggler.blur();
    return false;
  });

Fixing Gap

Problem solved

Fixing edge and gap for both of two columns, is the harder part of this script, because it require deep knowledge about the design itself.

  // Fix gap between two columns
  function fixGapAndEdge() {
    const isActiveLeft = leftToggler.classList
      .contains("left_toggler_active");
    const isActiveRight = rightToggler.classList
      .contains("right_toggler_active");

    const mainToggler  = document.getElementById("main_toggler");
    const asideToggler = document.getElementById("aside_toggler");

    // Fix Gap
    mainToggler.classList.remove("pr-0");
    asideToggler.classList.remove("pl-0");

    if (!isActiveLeft && isActiveRight) {
      mainToggler.classList.add("pr-0");
      asideToggler.classList.add("pl-0");
    }

    // Fix Edge
    if (isActiveLeft) {
      mainToggler.classList.add("px-0");
      asideToggler.classList.add("px-0");
    } else {
      mainToggler.classList.remove("px-0");
      asideToggler.classList.remove("px-0");
    }

    console.log("Fix gap and edge class.");
  }

But don’t worry. It is fixed now.

Bootstrap OC: Right Button: Active


6: Separation of Concern

Refactoring Consideration

Consider this event handler below:

  leftToggler.addEventListener("click", () => {
    leftToggler.classList.toggle("left_toggler_active");
 
    toggleLeftIcon();
    toggleLeftLayout(); 
    fixGapAndEdge();

    leftToggler.blur();
    return false;
  });

Why don’t I put the whole thing in just one functions. Of course I can do that, but the script becomes more unreadable, and getting hard to debug. So it is better that we focus each function, to its own task as below example:

  function toggleRightIcon() {
    const isActiveRight = ...
    ...
  }

  function toggleRightLayout() {
    const isActiveRight = ...
    ...
  }

  function fixGap() {
    const isActiveLeft = ...
    const isActiveRight = ...

    ...
  }

Of course there is a trade-off, by repeating those const above. But, it would be easier to understand.


What is Next ?

That is all. Our journey has complete here. Now it is a good time to apply the theme to your favorite framework. But this is a different journey on different article series.

What do you think?