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.
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.
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.
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:
- Left Left:
- Right Left:
- 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.
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:
To a wider view as this below, after clicking the left button (disable maxwidth).
And even wider content, after clicking the right button (hiding sidebar), as we wil see in the next on click event handler.
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
tocol-md-12
class. And add customsingle
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:
You can switch to this:
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.
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?