How do I retain toggle state across website? - javascript

So, I have a button that toggles between dark and light mode (my site is dark by default) and it works but now I need it to stay on whatever toggle state is selected across multiple pages. I suspect it has something to do with sessionstorage. Not using jQuery and don't want to. What can I just add to my code to make that happen?
I have five pages all linked to styles.css with an id of "dark" and then in the JS I'm referencing a second style sheet, light.css or 'light' in the JS, so I'm toggling the style sheets. All five pages have the button in the footer each with the same function written in one JS file.
HTML
<head>
<link rel="stylesheet" href="styles.css" id="dark">
</head>
<body>
<button onclick="toggle();" id="light-mode"><i class="fas fa-adjust"></i></button>
</body>
JavaScript
function toggle() {
var a = document.getElementById("dark");
a.x = 'light' == a.x ? 'styles' : 'light';
a.href = a.x + '.css';
}
This works perfectly, just not sure how to integrate sessionstorage or localstorage to save the toggle state across the site. What is the best method proceeding from this point if possible or should I do a complete overhaul?
UPDATE
So, tried #Reality's answer and not working but I think I might just be integrating it wrong. Very new to JS.
localStorage.setItem('toggled','true');
function toggle() {
var a = document.getElementById("dark");
a.x = 'light' == a.x ? 'styles' : 'light';
a.href = a.x + '.css';
if (localStorage.getItem('toggled') === 'false') {
localStorage.setItem('toggled','true');
} else {
localStorage.setItem('toggled','false');
}
}
SOLVED
Ok, anyone wanting to know how to toggle a theme perfectly where it works across your website, just follow this: https://dev.to/albertomontalesi/add-dark-mode-to-your-website-with-just-a-few-lines-of-code-5baf
When you follow that, you should do an additional thing to have your theme load better when toggled to. Just link to your second style sheet in the head of every page and add disabled="disabled" If you don't add this to the above approach, I found that when toggling to whatever theme and then navigating around the site, you basically have to load a new style sheet and you get a flicker and can even see the bare HTML version of the site momentarily, so this addition fixes that because by putting it in the head it is already loaded from the beginning.
FINAL
Here's what ended up working:
<head>
<link rel="stylesheet" href="dark-theme.css" type="text/css" id="theme">
<link rel="stylesheet" href="light-theme.css" type="text/css" disabled="disabled">
<script>
document.addEventListener('DOMContentLoaded', () => {
const themeStylesheet = document.getElementById('theme');
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
themeStylesheet.href = storedTheme;
}
const themeToggle = document.getElementById('theme-toggle');
themeToggle.addEventListener('click', () => {
if (themeStylesheet.href.includes('dark')) {
themeStylesheet.href = 'light-theme.css';
} else {
themeStylesheet.href = 'dark-theme.css';
}
localStorage.setItem('theme', themeStylesheet.href);
});
});
</script>
</head>
Per #Reality's suggestion, placing the JS in the head improved performance better than what the SOLVED edit suggested doing, however, the code in the link provided (https://dev.to/albertomontalesi/add-dark-mode-to-your-website-with-just-a-few-lines-of-code-5baf) is what is used in the above example. Still needed the disabled="disabled" in the light-theme.css link or else it would override the dark-theme.css and the toggle wouldn't occur all together. This was true of all the code examples seen in this thread that were tried.

You can set it using localStorage.setItem(); and localStorage.getItem().
localStorage.setItem('toggled','true');
//or
localStorage.setItem('toggled','false');
inside your toggle function:
//get the style by it's id
let a = document.getElementById("dark");
if(localStorage.getItem('toggled') === 'false'){
//set toggled to true, because the user clicked it when dark theme is on
localStorage.setItem('toggled','true');
//toggled is now on, in this case, light theme is switched on
//do something here
a.href = "light.css";
}
else{
//set toggled to false, because the user clicked it when light theme is on
localStorage.setItem('toggled','false');
//if toggled is off, in this case, dark theme is switched on
a.href = "styles.css";
}
It seems confusing at first, but just pay attention to the logic. It'll start to unfold in your mind as you read and reread it :)
If you want to switch to session storage (for that tab instance. Once you close the tab, session storage for that tab is deleted), then just replace the "localStorage" with "sessionStorage". Please keep in mind that localStorage and sessionStorage stringify values (convert them into a string) before storing them.

Related

Chrome sometimes not requesting CSS pages

This is an ASPX page and rarely the stylesheets do not load. No Request either. I can look at the network log in chromes debug and see that it didnt request it or load it from cache. Everything else, img , js, whatever all loads without problem. The only thing that this site does that I have never done before is load a stylesheet via javascript and that will request and work every time. There is also an Iframe in the page and that css always works as well. It feels like a 1 in 10 chance for this to happen, but its random. I cant remember if it happens when i run it locally or not, but it will happen on the 3 different IIS servers. In the sources of the Chrome DevTools it shows the file in there as well. Is this a bug in chrome or something? Is it a weird thing with closing tag in the links? Has anyone seen this before?
<head>
....
<link type="text/css" rel="stylesheet" href="~/css/site.min.css?v=#Model.buildTag" />
<link href="~/lib/tabulator/css/tabulator.css" rel="stylesheet" />
<link id="glcss" type="text/css" rel="stylesheet" href="/css/empty.css">
</head>
This JS code runs mid way trough the initial js load of the page before doc render and it sets a stylesheet to the page. Not sure if this would cause chrome to randomly abandon the other css documents. This css always works.
var stylesheetPath = _pth + "/css/custom_gl_theme_" + dd.value + ".min.css?v=" + _v;
$('link[id="glcss"]').attr('href', stylesheetPath);
this.value = dd.value;
Still don't know the cause, but this is the solution we had put in place. Basically, once the page loads, we check to see if those css files loaded properly, and if not, reset the url and it would force the pull.
$(window).on('load', function (e) {
var links = document.getElementsByTagName('link');
for (var i = 0, linkLen = links.length; i < linkLen; i++) {
var link = links[i];
if ((link.id == "") && (link.type == "text/css")) {
var cssHref = link.href;
link.href = cssHref;
}
}
});

AJAX back button

I've been reading about history.pushState and popstate but for a web-page that's populated with mostly AJAX requests (say a book store that allows users to add items to a cart), is there a function with the history object or something similar, that can rewind these additions?
A simpler representation of this is in the below example. A user clicks on a div and "Shown" appears in the respective div. Below these divs is the text, You have selected [detail]. This changes with respect to the selection and triggering the popState changes the text in the right way.
But the divs still have "Shown". Is there a way to remove these in order as they were added similar to how the line of text changes, or do I have to create a separate function to take care of this - which would be the opposite of the function that added "Shown" to the div?
I've added some function that I've edited in to achieve the above but I just don't like how hacky it is.
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class='name'></div>
<div class='age'></div>
<div class='sex'></div>
<p>You have selected <span>no color</span></p>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script>
$('div').on('click', function() {
var detail = $(this).attr('class');
showDetails(detail);
doPushState(detail);
});
function showDetails(detail) {
var classDeets = '.'+detail
$(classDeets).text("Showing " + detail);
$('p span').text(detail);
}
function doPushState(detail) {
var state = { selected : detail },
title = "Page title",
path = "/" + detail;
history.pushState(state, title, path);
}
function removeDetails(detail) {
var classDeets = '.'+detail
$(classDeets).text("");
}
function checkState(detail) {
var temp = document.getElementsByClassName(detail)[0].innerHTML;
if(temp.length == 0){
showDetails(detail);
}
else{
removeDetails(detail);
}
}
$(window).on('popstate', function(event) {
var state = event.originalEvent.state;
if (state) {
checkState(state.selected);
}
});
</script>
I'm not sure exactly what your use case is, but I immediately think of ajax loaded report and list pages that I do. I encode the options for the report after a "#". It makes the browser buttons work as intended for updating url based and it makes it linkable to others. You then just need to poll the # encoded data to detect changes and update accordingly. Since you use the # (in page link) it doesn't trigger a page load to the server. You just have to make sure that if you use encoded hash data that you don't use in-page links for page navigation. If this sounds like an option, let me know and I can post some helper code for setting it up.
Without more context, it's tough to rewrite your code. That said, you need to use history.replaceState() instead of history.pushState().
history.replaceState() modifies the state of a history entry. From MDN: "replaceState() is particularly useful when you want to update the state object or URL of the current history entry in response to some user action."

Detect if browser disables images

I need to detect if the browser disables images on the current page. Is this possible with JavaScript (jQuery/Modernizr)?
Problem: The project uses image sprites for icons and displays them like <i class="icon-user"></i>. The app is partially used by mobile users and some of them disable images if they're surfing with edge. As a result some buttons disappear.
The only thing I need is to display something (a gray square, circle…) if images are blocked. Via CSS class on the <html> element or so (class="no-images" e.g.). Writing alternative text between <i>…</i> is unhappily no option.
Thanks for your thoughts! I hope it exists a simple solution.
You can use the following script:
function noimage()
{
if ((document.getElementById('flag').offsetWidth==1
&& document.getElementById('flag').readyState=='complete')
||(document.getElementById('flag').offsetWidth==1
&& document.getElementById('flag').readyState==undefined))
{
var objHead = document.getElementsByTagName('head');
var objCSS = objHead[0].appendChild(document.createElement('link'));
objCSS.rel = 'stylesheet';
objCSS.href = 'alt.css';
objCSS.type = 'text/css';
}
}
And you need the following html markup:
<body onload="noimage();">
<img id="flag" src="clear.gif" alt="">
Just add it to the body. What this does is check the offsetWidth property to look for a 1x1 pixel image at the top of the browser. If it returns true it means that images are enabled.
If you need more info go here.

Change Twitter Widget appearance on-the-fly

I have the following code in my document:
<a class="twitter-widget" href="url" data-widget-id="138843679974442730">Twitter Timeline</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
data-widget-id is connected to one style. Right now if there is a theme change on the website (I replace all responsible stylesheets and images) everything changes but the Twitter widget.
Since the widget itself is an iframe, I can't change any stylesheets attached to it.
Is there an easy way to change the style of the widget without reloading it (deleting the tag, creating the tag, running js)?
You can style elements in the Twitter widget iframe using JavaScript.
First, you need the active document in the iframe's nested browsing context:
var doc = document.getElementById("twitter-widget-0").contentDocument;
Then, you can apply styles (e.g.):
doc.querySelector(".timeline-header").style["background-color"] = "black";
doc.querySelector(".timeline-header a").style["color"] = "white";
Example: http://codepen.io/smockle/pen/IJHnj
There is no straight forward way of doing this, so I've decided to bring in another dependency that will be delaying the onload event..
<script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
And here is the code that did the job:
var twitterBox = document.getElementsByClassName("twitterBox");
if (!twitterBox || twitterBox.length == 0) { return true; }
var twitterTimeline = document.createElement('a');
twitterTimeline.className = 'twitter-timeline';
twitterTimeline.href = 'url';
twitterTimeline.innerHTML = 'Twitter Timeline';
twitterTimeline.setAttribute('data-widget-id', '388742673974046720');
twitterBox[0].removeAttribute('data-twttr-id');
twitterBox[0].innerHTML = '';
twitterBox[0].appendChild(twitterTimeline);
twttr.widgets.load();

Automatically install toolbarbutton to firefox nav-bar, not working with insertItem

So I've read just about everything I could find on MDN, stackoverflow, etc, and it all seems to be outdated and/or not working. Here is the issue:
I want to automatically put my extension's "toolbarbutton" on the nav-bar when it is installed, similar to Chrome. What the user does after that point is up to them, although if you remove the button (with this particular extension) you might as well just remove the extension as it's useless without the button. ANYWAY...
There seems to be two methods for doing this. You can append it to the "currentSet" and make it persist, like this:
var currentset = document.getElementById("nav-bar").currentSet;
currentset=currentset + ",MYBUTTON_ID";
document.getElementById("nav-bar").setAttribute("currentset",currentset);
document.getElementById("nav-bar").currentSet = currentset;
document.persist("nav-bar","currentset");
Or, you can use "insertItem" like this:
var toolbar = document.getElementById("nav-bar");
toolbar.insertItem("MYBUTTON_ID", null);
toolbar.setAttribute("currentset", toolbar.currentSet);
document.persist(toolbar.id, "currentset");
Now, if I use the first method, it works, but it completely erases everything else on the nav bar for some reason. "currentSet" doesn't seem to have the other nav-bar buttons on it when it goes to overwrite it, and so I just end up with the default nav-bar plus my icon. Wiping out all of a user's other buttons is no good...
The second option doesn't wipe out the other options, but for whatever reason, the "insertItem" way of doing it doesn't work at all. My button never appears in the nav-bar, period.
Any ideas?
Alright, like I thought, there was nothing wrong with my code, and the problem was exactly as I described.
When the extension is loaded and the script to install the button is executed, it's done too early. At the time of execution, "currentSet" only contains the default buttons. No other extension buttons are loaded yet. As a result, if you modify the currentSet and save (persist) it, you wipe out all other buttons.
The solution (for me) was to force my "install" script to wait longer. I found that once the page had been loaded, all of the other buttons had enough time to appear. So, I simply did this:
function installButton() {
var navbar = document.getElementById("nav-bar");
var newset = navbar.currentSet + ",MYBUTTONID";
navbar.currentSet = newset;
navbar.setAttribute("currentset", newset );
document.persist("nav-bar", "currentset");
}
window.addEventListener("load", function () { installButton(); }, false);
well I use this code to add toolbar button in navbar, but this works only for the first time for the fresh installation and not for the next installation i.e. upgrading of the addon as user can move/drag the icon to different location. So, you need to try this in the new firefox profile. Here is the code:
//plcae toolbar icon
var navbar = document.getElementById("nav-bar");
var newset = navbar.currentSet + ",MYBUTTON_ID";
navbar.currentSet = newset;
navbar.setAttribute("currentset", newset );
document.persist("nav-bar", "currentset");
and here is the code for XUL Overlay:
<toolbarpalette id="BrowserToolbarPalette">
<toolbarbutton id="MYBUTTON_ID" inserbefore="searchBar" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="MYBUTTON_ID" tooltiptext="MYBUTTON_ID"
onclick="MYBUTTON_ID()"/>
</toolbarpalette>
or you can force the icon to display in nav bar against user will, this will take effect after each firefox restart, however not recommended
var navbar = document.getElementById("nav-bar");
var newset = navbar.currentSet;
if (newset.indexOf("MYBUTTON_ID") == -1)
{
if (newset.indexOf("reload-button,stop-button,") > -1)
newset = newset.replace("reload-button,stop-button,", "reload-button,stop-button,MYBUTTON_ID,");
else
newset = newset + ",MYBUTTON_ID";
navbar.currentSet = newset;
navbar.setAttribute("currentset", newset );
document.persist("nav-bar", "currentset");
}

Categories