Preface
Goal: Toggle layout apperance with plain javascript.
This is more like a javacript tutorial, which use a bunch of Bulma 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 Bulma class, and two feathericons.
<!-- toggler-->
<section class="mx-1 my-1">
<button class="button is-outlined is-pulled-left">
<span class="icon is-small">
<i class="fas fa-angle-double-left"></i>
</span>
</button>
<button class="button is-outlined is-pulled-right">
<span class="icon is-small">
<i class="fas fa-angle-double-right"></i>
</span>
</button>
<div class="is-clearfix"></div>
</section>
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="mx-1 my-1 is-hidden-mobile">
...
</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="button is-outlined is-pulled-left
left_toggler_active">
...
</button>
Right Button: right_toggler
ID with right_toggler_active
class.
<button id="right_toggler"
class="button is-outlined is-pulled-right
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 fa-angle-double-right
to fa-angle-double-left
, and flip back on the next click.
function toggleLeftIcon() {
const isActiveLeft = leftToggler.classList
.contains("left_toggler_active");
const leftIcon = leftToggler
.getElementsByTagName("i")[0];
if (isActiveLeft) {
leftIcon.classList.remove("fa-angle-double-right");
leftIcon.classList.add("fa-angle-double-left");
console.log("Left toggler class is active");
} else {
leftIcon.classList.remove("fa-angle-double-left");
leftIcon.classList.add("fa-angle-double-right");
console.log("Left toggler class is inactive");
}
}
Right Icon
On the opposite icon,
this will flip AwesomeIcon from fa-angle-double-left
to fa-angle-double-right
, and flip back on the next click.
function toggleRightIcon() {
const isActiveRight = rightToggler.classList
.contains("right_toggler_active");
const rightIcon = rightToggler
.getElementsByTagName("i")[0];
if (isActiveRight) {
rightIcon.classList.remove("fa-angle-double-left");
rightIcon.classList.add("fa-angle-double-right");
console.log("Right toggler class is active");
} else {
rightIcon.classList.remove("fa-angle-double-right");
rightIcon.classList.add("fa-angle-double-left");
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 Bulma 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 role="navigation" aria-label="main navigation"
class="navbar is-fixed-top is-white maxwidth maxwidth_toggler
white z-depth-3 hoverable">
...
</nav>
<!-- main -->
<div class="columns is-8 layout-base maxwidth maxwidth_toggler">
...
</div>
<!-- footer -->
<footer class="site-footer">
<div class="navbar is-fixed-bottom maxwidth maxwidth_toggler
is-white has-text-centered is-vcentered
white z-depth-3 hoverable">
...
</div>
</footer>
Sidebar: main_toggler
, and aside_toggler
.
<main role="main"
id="main_toggler"
class="column is-two-thirds">
...
</main>
<aside id="aside_toggler"
class="column is-one-thirds">
...
</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
is-hidden-tablet
class. -
Change width of main content from
is-two-thirds
tois-full
class.
function toggleRightLayout() {
const isActiveRight = rightToggler.classList
.contains("right_toggler_active");
const mainToggler = document.getElementById("main_toggler");
const asideToggler = document.getElementById("aside_toggler");
if (isActiveRight) {
mainToggler.classList.add("is-two-thirds");
mainToggler.classList.remove("is-full");
asideToggler.classList.remove("is-hidden-tablet");
} else {
mainToggler.classList.add("is-full");
mainToggler.classList.remove("is-two-thirds");
asideToggler.classList.add("is-hidden-tablet");
}
}
Now from the normal view as below:
You can switch to this:
I usually enjoy wider view while reading on the internet.
5: Fix Gap in Layout
Just in case you didn’t notice, my design has a flaw with code above. The gap between column is bigger than it should be.
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();
fixGap();
leftToggler.blur();
return false;
});
rightToggler.addEventListener("click", () => {
rightToggler.classList.toggle("right_toggler_active");
toggleRightIcon();
toggleRightLayout();
fixGap();
rightToggler.blur();
return false;
});
Fixing Gap
Problem solved
Fixing gap between two columns, is the harder part of this script, because it require deep knowledge about the design itself.
function fixGap() {
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");
mainToggler.classList.remove("pr-0");
asideToggler.classList.remove("pl-0");
if (!isActiveLeft && isActiveRight) {
mainToggler.classList.add("pr-0");
asideToggler.classList.add("pl-0");
}
console.log("Fix gap class.");
}
But don’t worry. It is fixed now.
Official Spacing Helpers
Update
This article series was made in June 2019 with Bulma 0.7.
In June 2020, Bulma 0.9 comes out. And Bulma 0.9 is equipped with spacing helpers.
6: Separation of Concern
Refactoring Consideration
Consider this event handler below:
leftToggler.addEventListener("click", () => {
leftToggler.classList.toggle("left_toggler_active");
toggleLeftIcon();
toggleLeftLayout();
fixGap();
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 as below example.
function rightResizerToggler() {
rightResizer.classList
.toggle("right_resizer_active");
const isActive = rightResizer.classList
.contains("right_resizer_active");
const rightIcon = rightResizer.getElementsByTagName("i")[0];
const mainResizer = document.getElementById("main_resizer");
const asideResizer = document.getElementById("aside_resizer");
if (isActive) {
rightIcon.classList.remove("fa-angle-double-left");
rightIcon.classList.add("fa-angle-double-right");
mainResizer.classList.add("is-two-thirds");
mainResizer.classList.remove("is-full");
asideResizer.classList.remove("is-hidden-tablet");
console.log("right resizer class is active");
} else {
rightIcon.classList.remove("fa-angle-double-right");
rightIcon.classList.add("fa-angle-double-left");
mainResizer.classList.add("is-full");
mainResizer.classList.remove("is-two-thirds");
asideResizer.classList.add("is-hidden-tablet");
console.log("right resizer class is inactive");
}
rightResizer.blur();
}
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.
Conclusion
What do you think?
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.
Farewell. We shall meet again.
What is Next ?
Update in 2022
Years gone by, and I have been stop writing. But suddenly, I have made a few animation enhancements.
It is not a part of bulma tutorial, bu I use this Bulma tutorial as a base, so I do not need to start from scratch.
Consider continue reading [ Animation Observer - Part One ].
Thank you for reading.