How to permanently swap a darkmode logo with javascript? - javascript

So I have the following code that manages my dark mode (please note I didn't write the original code, I'm just modifying):
if (toggleTheme) {
toggleTheme.click(function () {
darkMode();
});
};
// Theme Switcher
function darkMode() {
const logo = document.getElementById('logo'); // I Added this line
if (html.hasClass('dark-mode')) {
html.removeClass('dark-mode');
localStorage.removeItem("theme");
$(document.documentElement).removeAttr("dark");
logo.src = logo.dataset.logoLight; // I Added this line
} else {
html.addClass('dark-mode');
localStorage.setItem("theme", "dark");
$(document.documentElement).attr("dark", "");
logo.src = logo.dataset.logoDark // I Added this line
}
}
Here's the relative HTML (note, I'm building with Hugo so included is the Go Templating):
{{- $logo := (resources.Get site.Params.logo).Resize "270x webp q100" -}}
{{- $logoDark := (resources.Get "/images/logo-dark.png").Resize "270x webp q100" -}}
<img id="logo" class="logo__image" src="{{- $logo.RelPermalink -}}" data-logo-light="{{- $logo.RelPermalink -}}" data-logo-dark="{{- $logoDark.RelPermalink -}}" alt="{{ .Site.Title }}" height="{{- div $logo.Height 2 -}}" width="{{- div $logo.Width 2 -}}">
I've only added the three lines noted above to the JavaScript.
So that works exactly like I want. When someone toggles the button for dark mode and the logo flips to the darkmode version.
Great.
Except when they click to a new page, the original logo is in place.
Not sure how to store, check for, and retrieve the appropriate logo because I'm not very good with JavaScript (I just kind of hack together what I need).
How would you do this?

There are a couple ways to add the functionality that you're missing, but the most straightforward way given your current implementation is probably to use the theme value from localStorage.
Your code is already using localStorage to store the current theme. The bit you're missing is some JavaScript that checks the theme value in localStorage and then sets the logo path appropriately. You can use that stored value to determine what the theme is when any page loads by putting some JavaScript in the <head> of every page, or by adding it to the JavaScript file where your existing dark mode toggle logic is:
// Define a function that will check localStorage and set the logo path
function checkThemeAndSetLogo() {
const logo = document.getElementById('logo');
const currentTheme = localStorage.getItem("theme"); // get the theme value from localStorage
if (currentTheme === "dark") {
logo.src = logo.dataset.logoDark;
} else {
logo.src = logo.dataset.logoLight;
}
}
// Call the function so that the logo is updated appropriately
checkThemeAndSetLogo();
If you're interested in further reading and additional options for dark mode, I recommend A Complete Guide to Dark Mode on the Web.

i think you can always store a flag var that check if page was toggled to dark-mode, i show you:
// Theme Switcher
function darkMode(){
if (html.hasClass('dark-mode')){
html.removeClass('dark-mode');
localStorage.removeItem("theme");
$(document.documentElement).removeAttr("dark");
localStorage.setItem('dark_mode', 'false');
darkModeLogo();
}else{
html.addClass('dark-mode');
localStorage.setItem("theme", "dark");
$(document.documentElement).attr("dark", "");
localStorage.setItem('dark_mode', 'true');
darkModeLogo();
}
}
//runs everytime the js file is loaded
function darkModeLogo(){
const logo = document.getElementById('logo'); // I Added this line
const DARK_MODE = localStorage.getItem('dark_mode');
if(eval(DARK_MODE)){
logo.src = logo.dataset.logoLight; // I Added this line
}else{
logo.src = logo.dataset.logoDark // I Added this line
}
}
darkModeLogo();

Related

Need help making CSS file persistent in Javascript

I have an HTML page with two CSS files, one for a light theme and one for a dark theme. When I click the respective button, the theme changes to either light.css or dark.css.
However, if I reload the site, it goes back to the light theme, even though it was set to dark, is there any possible way to make this work the way I intended.
function toggleTheme() {
// Obtains an array of all <link>
// elements.
// Select your element using indexing.
var theme = document.getElementsByTagName("link")[0];
// Change the value of href attribute
// to change the css sheet.
if (theme.getAttribute("href") == "light.css") {
theme.setAttribute("href", "dark.css");
} else {
theme.setAttribute("href", "light.css");
}
You could save theme to localstorage like this:
const theme = document.getElementsByTagName('link')[0];
const lightTheme = 'light.css';
const darkTheme = 'dark.css';
const newTheme = theme.getAttribute('href') === lightTheme ? darkTheme : lightTheme;
theme.setAttribute('href', newTheme);
// set new theme as preferred
localStorage.setItem('theme', newTheme);
And after page creation we could check existence of preferred theme:
const theme = document.getElementsByTagName('link')[0];
const themeValue = localStorage.getItem('theme');
if (themeValue) {
theme.setAttribute('href', themeValue);
}

Where should I put the localStorage in this code?

I am trying to make the dark/light mode theme on my site store locally so that it doesnt untoggle every time a new page is opened or the site is refreshed. Where should I put the localStorage.? Or is the whole thing wrong?
const checkbox = document.getElementById('checkbox');
checkbox.addEventListener('change',()=> {
// change theme of website
document.body.classList.toggle('dark');
});
if you're using vanilla javascript i.e on you html you've loaded your script as below
<html>
...
<script src='/to/scripts.js'></script>
</body>
</html>
Then you can add two functions, one which set the them to localStorage and the other which reads it and make it accessible.
on update the update function sets the new value for the theme,
on read the read function reads the stored value even after refresh/reloads
as below
<html>
....
<script>
//for setting
const toggleTheme = () => {
if(localStorage.getItem('theme') === 'dark'){
localStorage.setItem('theme','light')
}else{
localStorage.setItem('theme','dark')
}
}
//for reading
const theme = () => localStorage.getItem('theme') || 'light'
</script>
<script src='/to/scripts.js'></script>
</body>
</html>
Edit (last answer was not solving the problem)
I think you can use DOMContentLoaded event to set the theme from localStorage, when the checkbox changes you set the selected theme on the storage. This way when the page reloads, it will look for the theme and set it, if theme is not set, it will default to light.
Here is a sample:
document.addEventListener("DOMContentLoaded", function(event) {
const theme = localStorage.getItem('theme');
if(theme === undefined) theme = 'light';
document.body.classList.add(theme);
});
const checkbox = document.getElementById('checkbox');
checkbox.addEventListener('change',(e) => {
const theme = e.target.checked ? 'dark' : 'light';
localStorage.setItem(theme);
document.body.classList.add(theme);
});

Power BI Embedded & JavaScript - How can I apply an action afterf a slicerState change on a particular visual?

I have a report that contains a custom toggle visual. The toggle has two possible values, "OFF" (by default) or "ON"
What I would like to do is link that toggle value to a report theme change. So when the value of that toggle value changes, the report theme will update to either light or dark mode.
I'm at a stage where I can get the slicerState of the particular visual and determine it's value as being on or off. However, when trying to apply a report theme update, it appears to loop endlessly ( (see recording here))
My feeling is that it's related to the report.on('rendered'....... function but I'm looking to get a bit of help if possible. With it being a custom visual, I'm unable to listen to a buttonClick or dataSelection (as far as i'm aware), so i guess the only way I can detect that the toggle has been used is to track the slicerState function?
here's is the block of code relating to what i'm trying to do:
// Get a reference to the embedded dashboard HTML element
var $embedContainer = $("#embedContainer");
//define report embed
var report = powerbi.embed($embedContainer.get(0), config);
//when report renders
report.on('rendered', async () => {
//get all pages
const pages = await report.getPages()
//get active page
let activePage = pages.filter(function(page) {
return page.isActive
})[0];
//get visuals on active page
let visuals = await activePage.getVisuals();
//find target visual
const TargetVisual = visuals.filter(function(visual) {
return visual.name === "259fea6751434e7910b4"
})[0];
//get current state of visual
const state = await TargetVisual.getSlicerState();
//get current state value (on or off )
let filteredValue = state.filters[0].values
console.log(filteredValue)
//when the toggle is switched ON, apply the light theme.
if (filteredValue == "ON") {
/*
//when clickon on
report.applyTheme({
themeJson: themes.find(theme => theme.name === "dark")
}); } else {
//when the toggle is switched OFF, apply the dark theme.
//when clickon off
report.applyTheme({
themeJson: themes.find(theme => theme.name === "light")
});
*/
}
........
var themes = [
{
"name": "light",
"dataColors": ["#93A299", "#CF543F", "#B5AE53", "#848058", "#E8B54D", "#786C71", "#93A2A0", "#CF9A3F", "#8CB553", "#728458", "#D0E84D", "#786D6C"],
"background": "#FFFFFF",
"foreground": "#CF543F",
"tableAccent": "#93A299"
},
{
"name": "dark",
"dataColors": ["#31B6FD", "#4584D3", "#5BD078", "#A5D028", "#F5C040", "#05E0DB", "#3153FD", "#4C45D3", "#5BD0B0", "#54D028", "#D0F540", "#057BE0"],
"background": "#000000",
"foreground": "#4584D3",
"tableAccent": "#31B6FD"
}
]
It is not possible to track the slicer state which is applied at the runtime in Power BI embed. getSlicerState will give you an empty array of filters if you change the state of slicer at runtime. If you set the slicer state at compile time using setSlicerState then getslicerState will give you an array of filters and using that you can apply the theme.
if (state.filters.length > 0) {
report.applyTheme({
themeJson: themes.find(theme => theme.name === "dark")
});
Please find the references:
https://community.powerbi.com/t5/Developer/Power-BI-Embedded-Tracking-visual-filters-slicers/m-p/802196
https://learn.microsoft.com/javascript/api/overview/powerbi/control-report-slicers

Adding LocalStorage to a Dark Mode Toggle

I am currently working on adding a dark theme toggle to my website and can't seem to figure out the best way to store the data to LocalStorage. All I am using is a button that toggles the class of "dark" on the main document :root. The actual toggle works perfectly fine, I am only struggling with getting that selected option to stay in LocalStorage.
Here is my code with the LocalStorage that I am trying to add (which does save the key of theme to LocalStorage but not the actual class toggle).
document.addEventListener("DOMContentLoaded", function () {
const theme = localStorage.getItem("theme", "dark");
const darkModeToggle = document.getElementById("modeSwitch");
darkModeToggle.addEventListener("click", function () {
document.documentElement.classList.toggle("dark");
if (document.documentElement.classList.contains("dark")) {
localStorage.setItem("theme", "dark");
}
});
});
Here are my CSS variables as well for reference.
:root {
--main-background: #f8fafb;
--app-background: #ffffff;
--app-background-alt: #fcfcfc;
--app-background-hover: #f8fafb;
--dark-background: #141923;
--main-color: #000000;
--secondary-color: #747987;
}
.dark:root {
--main-background: #141923;
--app-background: #171b2c;
--app-background-alt: #1c2031;
--app-background-hover: #1d213d;
--dark-background: #141923;
--main-color: #ffffff;
--secondary-color: #747987;
}
Thank you so much.
Remove second parameter, should be like that:
const theme = localStorage.getItem("theme");
When you get item from LocalStorage need just key
the way you are storing the theme in the localStore is fine, if want to do it in other way, you could store the theme in an object. Here and example:
localStorage.setItem("theme",JSON.stringify({theme: "dark"}));
To be honest, i think that if your application is not too big you can leave it the way is it.
After playing around a bit, I did figure out a way to successfully store the theme into localStorage. What I am doing is checking to see if the toggle was clicked and "dark" was added to the class list. If it was, I store that in localStorage as "theme", "dark" - and if it "dark" was not added to the class list, I store it in localStorage as "theme", "light".
Then, at the top of the function I check to see if the theme contains "dark" - If the theme contains "dark", I add the class "dark" to the :root, and if it does not I remove it.
I am not sure if this is considered the "correct" way, but it does work!
document.addEventListener("DOMContentLoaded", function () {
const theme = localStorage.getItem("theme", "dark");
const darkModeToggle = document.getElementById("modeSwitch");
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
darkModeToggle.addEventListener("click", function () {
document.documentElement.classList.toggle("dark");
if (document.documentElement.classList.contains("dark")) {
localStorage.setItem("theme", "dark");
} else {
localStorage.setItem("theme", "light");
}
});
});

Replace an img src on scroll

I am trying to replace logo-text-black src attribute so that the svg img changes as the user scrolls. Is it possible to add this to my current script?
img/logo-text-white.svg // Top State
img/logo-text-black.svg // Scroll State
HTML
<nav class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img class="logo" src="img/logo.svg">
<a href="#top"><img class="logo-text" src="img/logo-text-white.svg">
</a>
</div>
</nav>
JS
$(window).scroll(function() {
var value = $(this).scrollTop();
if (value > 100)
$(".navbar-default").css("background", "white"); // Scroll State
else
$(".navbar-default").css("background", "transparent"); // Top state
});
To replace image source you may use jQuery .attr method:
var initialSrc = "img/logo.svg";
var scrollSrc = "img/logo-text-black.svg";
$(window).scroll(function() {
var value = $(this).scrollTop();
if (value > 100)
$(".logo").attr("src", scrollSrc);
else
$(".logo").attr("src", initialSrc);
});
This approach requires only one <img> with logo class in the HTML:
<nav class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img class="logo" src="img/logo.svg">
</div>
</nav>
Ignoring the fact that the simple answer to the question asked is that you use the .attr function to change an attribute for an element when using jQuery, this is how I would go about accomplishing the task set forth in your question.
First, I would put all of this in a function (mainly to separate the variables and logic from other page scripts to prevent interference).
My next bit of advice would be to implement the background color change in two or more CSS classes. This has the benefit of simplifying the JavaScript, as well as keeping the styling part in the styling area.
Next, I like to make constant variables for my "magic words", so that if I change the word used later on I only have to change the word once in the code, instead of everywhere the word is used.
// cache the magic words
const DARK = 'dark';
const LIGHT = 'light';
I would put the image sources into an object where the keys are the magic words associated with those sources. This allows for quick and convenient lookup later.
// define our different sources for easy access later
const sources = {
light: "http://via.placeholder.com/150x50/fff/000?text=logo",
dark: "http://via.placeholder.com/150x50/000/fff?text=logo"
};
After that I would pre-load the images to prevent a visual delay the first time the source is changed.
// pre-load the images to prevent jank
document.body.insertAdjacentHTML('beforeend', `
<div style="display: none!important">
<img src="${ sources[LIGHT] }">
<img src="${ sources[DARK] }">
</div>
`);
It is important to note that performing tasks on-scroll can cause problems.
The main problems are:
The effects can be blocking, which means that process heavy tasks will cause "scroll jank". This is where there is a visual inconsistency with how the page scrolls.
It is possible for the scroll event to fire while there is already a scroll event listener executing. This may cause the two executions to interfere with each other.
Combatting these problems is easy:
To prevent scroll-jank, wrap the handler in a setTimeout call. This will move the execution of the handler to the top of the stack to be executed at the next earliest convenience.
To prevent multiple handlers from running simultaneously, define a "state" variable outside of the handler to keep track of execution state.
This variable will be set to true when an event handler is executing and false when there is no event handler execution. When the handler execution begins, check the value of the state variable:
If it is true, cancel the execution of this handler call.
If it is false, set the state to true and continue.
Just make sure that wherever you may be exiting the function, you also reset the state variable.
// define our scroll handler
const scroll_handler = _ => setTimeout(_ => {
// if we are already handling a scroll event, we don't want to handle this one.
if (scrolling) return;
scrolling = true;
// determine which theme should be shown based on scroll position
const new_theme = document.documentElement.scrollTop > 100 ? DARK : LIGHT;
// if the current theme is the theme that should be shown, cancel execution
if (new_theme === theme) {
scrolling = false;
return;
}
// change the values
logo.src = sources[new_theme];
el.classList.remove(theme);
el.classList.add(new_theme);
// update the state variables with the current state
theme = new_theme;
scrolling = false;
});
After that, just assign the event listener.
Here it is all together:
function navbarSwitcher(el) {
// cache the reference to the logo element for use later
const logo = el.querySelector('.logo');
// cache the magic words
const DARK = 'dark';
const LIGHT = 'light'
// define our state variables
let scrolling = false;
let theme = LIGHT;
// define our different sources for easy access later
const sources = {
light: "http://via.placeholder.com/150x50/fff/000?text=logo",
dark: "http://via.placeholder.com/150x50/000/fff?text=logo"
};
// pre-load the images to prevent jank
document.body.insertAdjacentHTML('beforeend', `
<div style="display: none!important">
<img src="${ sources[LIGHT] }">
<img src="${ sources[DARK] }">
</div>
`);
// define our scroll handler
const scroll_handler = _ => setTimeout(_ => {
// if we are already handling a scroll event, we don't want to handle this one.
if (scrolling) return;
scrolling = true;
// determine which theme should be shown based on scroll position
const new_theme = document.documentElement.scrollTop > 100 ? DARK : LIGHT;
// if the current theme is the theme that should be shown, cancel execution
if (new_theme === theme) {
scrolling = false;
return;
}
// change the values
logo.src = sources[new_theme];
el.classList.remove(theme);
el.classList.add(new_theme);
// update the state variables with the current state
theme = new_theme;
scrolling = false;
});
// assign the event listener to the window
window.addEventListener('scroll', scroll_handler);
}
// attach our new plugin to the element
navbarSwitcher(document.querySelector('.wrap'));
body {
height: 200vh;
}
.wrap {
width: 100%;
position: fixed;
}
.wrap.light {
background-color: white;
}
.wrap.dark {
background-color: black;
}
<div class="wrap light">
<img class="logo" src="http://via.placeholder.com/150x50/fff/000?text=logo">
</div>

Categories

Resources