Where to Discuss?

Local Group

Preface

Step Seven: Toggle layout apperance with plain javascript.

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


1: Chapter Overview

We have done a lot of, nunjucks templates, and also Sass. This time we deal with javascript.

I have made two variations of this toggle javascript.

  • For this article, I use sm.
  • But for my real blog, I use md instead.

Source Code: Step-07

To obtain the source code for this chapter, please follow the link provided below:

The obsolete Bootstrap v4 article:

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.

Additional Assets

All we need just this one custom toggler javascript:

❯ tree -C assets/js
assets/js
├── bootstrap.min.js
├── custom-toggler.js
└── popper.min.js

1 directory, 3 files

Bootstrap SASS: Javascript Directory Structure

Append this javascript in our HTML head.

{% block htmlhead %}
  {% include './heads/meta.njk' %}
  {% include './heads/links.njk' %}
  <script src="assets/js/custom-toggler.js"></script>
{% endblock %}

Bootstrap5: Nunjucks: Blocks: Javascript Toggler


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.

Stylesheet

The responsible stylesheet for this is:

@include media-breakpoint-up(md) {
   // Desktop
  .maxwidth {
    max-width: map-get($grid-breakpoints, "md");
    margin-right: auto;
    margin-left: auto;
  }
}

Bootstrap SASS: Custom SASS for Layout Page

For wider maxwidth you can replace the md with xl.

Blocks

Nunjucks Document Source

We need to specify maxwidth-toggler class on the element.

{% block content %}
  <!-- responsive colored main layout -->
  <div class="row layout-base
              maxwidth maxwidth_toggler">
    {% include './contents/074-main.njk' %}
    {% include './aside/074-aside.njk' %}
  </div>
{% endblock %}

Bootstrap5: Nunjucks: Blocks: Javascript Toggler

This class does not need any stylesheet. We are going to use this class in javascript, to manipulate visibility of some elements, as we will see below.

Toggler Template

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

<!-- toggler -->
<section class="m-1 d-none d-sm-block">
  <button id="left_toggler"
          class="btn btn-sm btn-light
                  float-start text-dark
                  left_toggler_active">
    <span class="icon is-small">
      <i class="fas fa-angle-double-left"></i>
    </span>
  </button>
  <button id="right_toggler"
          class="btn btn-sm btn-light
                  float-end text-dark
                  right_toggler_active">
    <span class="icon is-small">
      <i class="fas fa-angle-double-right"></i>
    </span>
  </button>
  <div class="clearfix"></div>
</section>

Bootstrap5: Nunjucks: Blog Post: Toggler

You can use any icon set to suit your need, such as Feather Icons, Bootstrap Icons, or FontAwesome.

Bootstrap5: Blog Post: 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 text-dark
                   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 text-dark
                   right_toggler_active">
        ...
    </button>

For wider maxwidth you can replace the sm with md.

Javascript: Tailor Made

Our first javascript example should be simple. This script will handle onclick event on both element, in an unobstrusive fashioned. The simplified version of the code can be shown below:

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;
  }
});

Note that the real code will be longer, with additional functionality.

Bootstrap5: Custom Javascript Toggler

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.

Bootstrap5: Inspect Toggle Active 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:

Bootstrap5: Toggle: Left Right

  • Left Left:

Bootstrap5: Toggle: Left Left

  • Right Left:

Bootstrap5: Toggle: Right Left

  • Right Right:

Bootstrap5: 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.

Bootstrap5: 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

{% block content %}
  <!-- responsive colored main layout -->
  <div class="row layout-base
              maxwidth maxwidth_toggler">
    {% include './contents/074-main.njk' %}
    {% include './aside/074-aside.njk' %}
  </div>
{% endblock %}

And so is the header:

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

And also the footer:

{% block content %}
  <!-- footer -->
  <footer class="footer">
    <div class="text-dark text-center
                maxwidth maxwidth_toggler
                white z-depth-3 hoverable">
      &copy; 2021.
    </div>
  </footer>
{% endblock %}

Sidebar: main_toggler, main_toggler_wrapper, and aside_toggler.

<main id="main_toggler"
      class="col-sm-8 px-0">
  <section id="main_toggler_wrapper"
            class="main-wrapper blue">
        ...
  </section>
</main>
<aside id="aside_toggler" class="col-sm-4 px-0">
  <section class="aside-wrapper green">
      ...
  </section>
</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:

Bootstrap5: Left Button: Wide Normal

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

Bootstrap5: 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.

Bootstrap5: 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-sm-8 to col-sm-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-sm-8");
      mainToggler.classList.remove("col-sm-12");

      mainTogglerW.classList.remove("single");

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

      mainTogglerW.classList.add("single");

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

For wider maxwidth you can replace the sm with md. Or better with something like:

      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");
      }

Preview in Browser

Now from the normal view as below:

Bootstrap5: Right Button: Normal

You can switch to this:

Bootstrap5: Right Button: Hide

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.

Bootstrap5: Gap Issue

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.

Bootstrap5: Right Button: Edge Fix


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 🤔?

One simple thing you can do to enhance your simple blog is animation. We are going to explore this before we go down to dashboard.

Consider continue reading [ Bootstrap - Javascript Numerator ].