Toggle Button function doesn't maintain current State - javascript

I have two functions displayDataGraph and displayDataTable that displays data as a table and graph. I have a button that toggles between graph and table display.The graph is the default view and the switch to the table view happens with the toggle button.
Each page has a previous and next button that takes user to next 20 set of data or previous 20 set of data.
The expected behavior is that while viewing the data as a table, the next page will also display the data as table, and that while viewing the data as a graph, the next or previous page will also display the data as a graph.
The current behavior is that while viewing as a graph and I click on view as table button, the table is displayed. But when I click on next or previous, it takes me back to the graph instead of the next page of the table format. This doesn't happen with the graph. When I click on the next button while viewing as a graph, it takes me to the next page as the graph (perhaps because it's the default? )
Which means the toggling works just once, and when I click next or previous, it just goes back to the original/default view/state
Any idea why this behavior is happening or how it can be fixed?
My code is quite long and i don't know how much help sharing snippets would be, but here's the toggling function:
const toggleButton = () => {
const data = document.querySelectorAll("button.data");
const dataTable = document.querySelector(".dataTable");
const dataGraph = document.querySelector(".dataGraph");
if (dataTable && dataGraph) {
if (dataTable.style.display === "block") {
dataTable.style.display = "none";
dataGraph.style.display = "block";
data.forEach(
(element) => (element.innerText = "Display Data as Table")
);
} else {
dataGraph.style.display = "none";
dataTable.style.display = "block";
data.forEach(
(element) => (element.innerText = "Display Data as Chart")
);
}
}
};
I have other relevant functions such as
const displayDataTable
const displayGraphTable
const paginateData

You need to be able to save the desired view state across page refreshes. As Lauren Groh mentions, can you save it in Session Storage?
When your page loads it sounds like the css display property for the dataTable is being set to none and the css display property on the dataGraph is being set to block. That's fine but it's hardcoded into the pageload. Your javascript toggle button changes the css style properly but when you refresh the page, it loads everything up from the begining.
To solve it, you need to add some check at page load that looks at Session Storage before deciding whether the dataTable or dataGraph should be display none or block. Here is a an idea:
const data = document.querySelectorAll("button.data");
const dataTable = document.querySelector(".dataTable");
const dataGraph = document.querySelector(".dataGraph");
//extract the functionality:
const loadGraphView = () => {
dataTable.style.display = "none";
dataGraph.style.display = "block";
data.forEach((element) => (element.innerText = "Display Data as Table"));
// Save data to sessionStorage
sessionStorage.setItem("dataViewType", "graph");
};
const loadTableView = () => {
dataGraph.style.display = "none";
dataTable.style.display = "block";
data.forEach((element) => (element.innerText = "Display Data as Chart"));
sessionStorage.setItem("dataViewType", "table");
};
const toggleView = () => {
const viewType = sessionStorage.getItem("dataViewType");
if (viewType) {
if (viewType === "table") {
loadTableView();
} else if (viewType === "graph") {
loadGraphView();
}
}
};
//At some point when you load the page, call toggle view
//Then any time you click the button, have it call toggle view.
toggleView();
Saving the state to Session Storage will allow you to keep track of the desired view until the person closes their browser. If you want to mantain state after the browser closes, you can do something similar with Local Storage instead.
localStorage.setItem('myCat', 'Tom');
const cat = localStorage.getItem('myCat');
There are probably nicer ways to implement this code. I just tried to put it down in a way that fits with what you showed us and should allow you to take it from there and make it prettier. :-)

If there's a refresh involved, maybe the toggle is defaulting, as you said. You could store the desired state in SessionStorage so it sticks after refresh.

Related

Javascript pop up on multiple elements

I am trying to build a pop-up function written in JavaScript but I feel the code could be better.
So I have two thumbnails and when click it shows the corresponding image but bigger.
I want to build a page with about 20 of these but I feel there will be a lot of code repetition and I am stuck.
My code is here:
https://codesandbox.io/s/i8b1s
Here is my JS:
/*
The goal of this is to simplify the JS code as when I add more images
I do want to keep duplicating code.
*/
// Tesing
const targetNum = document.querySelectorAll("target");
console.log(targetNum);
// Original code below
// Target 1
const target1 = document.querySelector(".counterNo1");
const target2 = document.querySelector(".counterNo2");
// Target 1 Pop Up
const target1MainImage = document.querySelector(".mainImage1");
const target2MainImage = document.querySelector(".mainImage2");
// Close buttons
const close1 = document.querySelector(".closeBTN1");
const close2 = document.querySelector(".closeBTN2");
//Target 1 Clicked Event
target1.addEventListener("click", function () {
console.log("Target 1");
target1MainImage.classList.remove("hide");
target1MainImage.classList.add("show");
});
//Target 2 Clicked Event
target2.addEventListener("click", function () {
console.log("Target 2");
target2MainImage.classList.remove("hide");
target2MainImage.classList.add("show");
});
// Close
//Close Event 1
close1.addEventListener("click", function () {
console.log("Close Target 1");
target1MainImage.classList.add("hide");
target1MainImage.classList.remove("show");
});
//Close Event 2
close2.addEventListener("click", function () {
console.log("Close Target 2");
target2MainImage.classList.add("hide");
target2MainImage.classList.remove("show");
});
As you can see if I have more that one pop up, I am duplicating event listeners etc and I do not want a lot of dup code for elements. This is where I am stuck.
Can anyone point me in the right direction on what to do please?
Thanks,
Ben.
so what you can do is create a common popup and let there be 20 targets on the page. so all you have to do then is
// here create a variable to map the location or src value of bigger image
const ImageMapper = {
one: '/images/one.jpg'
.....
}
// then create the function which calls the popup and set the src of the image element in it
const openPopup = (opener) => {
popupImage.src = ImageMapper[opener]
popup.classList.add('show')
}
// and in the poup add a button which closes the popup so that you dont have to write multiple functions just to close a single popup
const closePopup = () => {
popup.classList.add('hide')
}
// and last in each of the possible element that will open the popup just add the onclick handler like
target1.onclick = () => {
openPopup('one')
}
// this will require a lot less code then what you will have to write

How to make sure the function "window.location.reload()" only fires once instead of having an infinite loop?

I was trying to add an accessibility feature to my web page, which is built with React.js, to make sure that the color of my banner doesn't get affected by the high contrast plugin of Chrome.
I tried to implement it that by adding a function to detect whether the Chrome plugin has been turned on, and my code should toggle a class to make sure the banner color won't change much in the high contrast mode. I noticed that this change could only be seen after refreshing the page, so I added this line of code window.location.reload() to manually reload the page.
The problem is, this part of code enters into an infinite loop and my page just won't stop reloading. I tried to replace the reloading part with other methods, the results were still the same. Here's the code of my React Component:
componentDidMount = () => {
this.keepBannerColor()
}
keepBannerColor() {
// these two lines of code below is tell whether the browser is chrome
let userAgentString = navigator.userAgent;
let chromeAgent = userAgentString.indexOf("Chrome") > -1;
let header = document.querySelector('.mizaru-header')
if (header && chromeAgent) {
console.log('funciton triggered')
let htmlTag = document.getElementsByTagName('html');
let isInverted = htmlTag[0].getAttribute('hc') != null
header.classList.toggle('inverted', isInverted)
window.location.reload()
}
}
Here's my CSS code for the toggle class: .inverted{ filter: invert(100%); }
Any help would be nice!
Why don't you try save an indicator into localstorage or sessionstorage, and add this validation in your if condition:
Snippet of your code:
...
// Use local or session storage
let hasReloaded = localStorage.getItem('hasReloaded')
if (header && chromeAgent && !hasReloaded) {
console.log('funciton triggered')
let htmlTag = document.getElementsByTagName('html');
let isInverted = htmlTag[0].getAttribute('hc') != null
header.classList.toggle('inverted', isInverted)
// Set hasRealoaded to true
localStorage.setItem('hasReloaded', true)
window.location.reload()
}
...
You don't need a page reload for this, you need MutationObserver.
This will look for changes in the document on a particular element.
As the hc attribute is added to the page dynamically this will listen for when it is added or removed.
The below will log "High Contrast", "a4" if the high contrast mode is switched on (with the "a4" changing depending on the settings) and "High Contrast Off" if the plugin is not active.
The beauty of the below is that depending on the setting "a3", "a4" etc. you can apply different styling.
There is something not right with the below as if fires "High Contrast Off" twice when the plugin is disabled so you will need to investigate that. (This is because when the plugin is toggled off it first sets the state to "a0" and then removes the attribute, under normal use it should be fine but just something to be aware of).
const targetNode = document.documentElement;
// Options for the observer (which mutations to observe)
const config = { attributes: true};
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
let name = "";
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
if(mutation.attributeName == "hc"){
if(mutation.target.attributes.getNamedItem(mutation.attributeName) != null && mutation.target.attributes.getNamedItem(mutation.attributeName).value != "a0"){
console.log("High Contrast", mutation.target.attributes.getNamedItem(mutation.attributeName).value);
}else{
console.log("High Contrast Off");
}
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);

Live Chat reload object to set new group

And Thanks in Advance :)
So I am using Live Chat and want to dynamically change the group based on a user action.
IF user has a chat open THEN keep current chat open (this part is fine)
If user clicks link A then they will get assigned to groups 1-5 depending on another variable
If user clicks link B then they will get assigned to group 6
Now I can get all of the above IFs to work independently....but when I try to change the group ID dynamically it doesnt take... I've tried resetting the object then reloading the library again to no avail :(
// Inside $(document).ready()
window.__lc = window.__lc || {};
window.__lc.license = LICENSE_ID;
window.__lc.group = live_chat_group;
window.__lc.chat_between_groups = false;
window.LC_API = window.LC_API || {};
window.LC_API.on_before_load = function() {
if (window.LC_API.visitor_engaged() === false && livechat_chat_started === false) {
window.LC_API.hide_chat_window();
}
};
window.LC_API.on_chat_started = function() {
livechat_chat_started = true;
};
$.getScript('https://cdn.livechatinc.com/tracking.js', function() {});
So the above gets loaded on page load to keep the current chat session between pages
$("body").on("click", "#sales-chat-init", function () {
window.__lc = {};
window.LC_API = {};
window.__lc.license = LICENSE_ID;
window.__lc.group = 2;
window.__lc.hostname = "";
window.__lc.chat_between_groups = false;
$.getScript('https://cdn.livechatinc.com/tracking.js?q=52895293523', function() {});
console.log(window.__lc);
//window.LC_API.open_chat_window();
});
The above doesnt work...window.__lc is just the object I created and not tied re-init'd as the LiveChat object.
I work at LiveChat so let me help you with that :)
There is no option to change a group in the chat window if the LiveChat script is already loaded. However there are two ways you can handle it:
A) ask your users to pick a group in a pre-chat survey
B) create kind of 'pre-chat' on your site and load the script after the group will be chosen (this is available only once per session), here's an example http://lp.labs.livechatinc.com/helpers/group_chooser/
Perhaps the user needs to leave the chat first
LC_API.close_chat();
then start again...
https://developers.livechatinc.com/javascript-api/#close-chat

Slidetoggle to retain current state when page is refreshed

I have this code, which works but goes back to initial closed state whenever I reload or refresh page. I want retain last toggle state even after page reload. Can't seem to get my head around this problem.
My code is:
var $answers = $('.contentbar');
$(".titlebar").click(function () {
var $ans = $(this).next(".contentbar").stop(true).slideToggle(500);
$answers.not($ans).filter(':visible').stop(true).slideUp();
})
There's a plugin called jQuery.cookie. Using that you can check if there's already a user-set accordion and based on the preference, you can show / hide stuff by default.
var $answers = $('.contentbar');
$(".titlebar").click(function () {
if (!$.cookie('contentbar'))
var $ans = $(this).next(".contentbar").stop(true).slideToggle(500);
else
{
var $ans = $($.cookie('contentbar')).next(".contentbar").stop(true).slideToggle(500);
$.cookie('contentbar', this);
}
$answers.not($ans).filter(':visible').stop(true).slideUp();
});

display message javascript while a calculation is being made

I have been looking around and I cannot seem to figure out how to do this, although it seems like it would be very simple.(mobile development)
What I am trying to do is display a message (kind of like an alert, but not an alert, more like a dialog) while a calculation is being made. Simply like a Loading please wait. I want the message to appear and stay there while the calculation is being done and then be removed. I just cannot seem to find a proper way of doing this.
The submit button is pressed and first checks to make sure all the forms are filled out then it should show the message, it does the calculation, then hides the message.
Here is the Calculation function.
function scpdResults(form) {
//call all of the "choice" functions here
//otherwise, when the page is refreshed, the pulldown might not match the variable
//this shouldn't be a problem, but this is the defensive way to code it
choiceVoltage(form);
choiceMotorRatingVal(form);
getMotorRatingType();
getProduct();
getConnection();
getDisconnect();
getDisclaimer();
getMotorType();
//restore these fields to their default values every time submit is clicked
//this puts the results table into a known state
//it is also used in error checking in the populateResults function
document.getElementById('results').innerHTML = "Results:";
document.getElementById('fuse_cb_sel').innerHTML = "Fuse/CB 1:";
document.getElementById('fuse_cb_sel_2').innerHTML = "Fuse/CB 2:";
document.getElementById('fuse_cb_result').innerHTML = "(result1)";
document.getElementById('fuse_cb_res_2').innerHTML = "(result2)";
document.getElementById('sccr_2').innerHTML = "<b>Fault Rating:</b>";
document.getElementById('sccr_result').innerHTML = "(result)";
document.getElementById('sccr_result_2').innerHTML = "(result)";
document.getElementById('contactor_result').innerHTML = "(result)";
document.getElementById('controller_result').innerHTML = "(result)";
//Make sure something has been selected for each variable
if (product === "Choose an Option." || product === "") {
alert("You must select a value for every field. Select a Value for Product");
**************BLAH************
} else {
//valid entries, so jump to results table
document.location.href = '#results_a';
******This is where the message should start being displayed***********
document.getElementById('motor_result').innerHTML = motorRatingVal + " " + motorRatingType;
document.getElementById('voltage_res_2').innerHTML = voltage + " V";
document.getElementById('product_res_2').innerHTML = product;
document.getElementById('connection_res_2').innerHTML = connection;
document.getElementById('disconnect_res_2').innerHTML = disconnect;
if (BLAH) {
}
else {
}
populateResults();
document.getElementById('CalculatedResults').style.display = "block";
} //end massive else statement that ensures all fields have values
*****Close out of the Loading message********
} //scpd results
Thank you all for your time, it is greatly appreciated
It is a good idea to separate your display code from the calculation code. It should roughly look like this
displayDialog();
makeCalculation();
closeDialog();
If you are having trouble with any of those steps, please add it to your question.
Computers are fast. Really fast. Most modern computers can do several billion instructions per second. Therefore, I'm fairly certain you can rely on a a setTimeout function to fire around 1000ms to be sufficient to show a loading message.
if (product === "Choose an Option." || product === "") {
/* ... */
} else {
/* ... */
var loader = document.getElementById('loader');
loader.style.display = 'block';
window.setTimeout(function() {
loader.style.display = 'none';
document.getElementById('CalculatedResults').style.display = "block";
}, 1000);
}
<div id="loader" style="display: none;">Please wait while we calculate.</div>
You need to give the UI main thread a chance to render your message before starting your calculation.
This is often done like this:
showMessage();
setTimeout(function() {
doCalculation();
cleanUp()
}, 0);
Using the timer allows the code to fall through into the event loop, update the UI, and then start up the calculation.
You're already using a section to pop up a "results" page -- why not pop up a "calculating" page?
Really, there are 4,000,000 different ways of tackling this problem, but why not try writing a "displayCalculatingMessage" function and a "removeCalculatingMessage" function, if you don't want to get all object-oriented on such a simple thing.
function displayCalculatingMessage () {
var submit_button = getSubmitButton();
submit_button.disabled = true;
// optionally get all inputs and disable those, as well
// now, you can either do something like pop up another hidden div,
// that has the loading message in it...
// or you could do something like:
var loading_span = document.createElement("span");
loading_span.id = "loading-message";
loading_span.innerText = "working...";
submit_button.parentElement.replaceChild(loading_span, submit_button);
}
function removeCalculatingMessage () {
var submit_button = getSubmitButton(),
loading_span = document.getElementById("loading-message");
submit_button.disabled = false;
loading_span.parentElement.replaceChild(submit_button, loading_span);
// and then reenable any other disabled elements, et cetera.
// then bring up your results div...
// ...or bring up your results div and do this after
}
There are a billion ways of accomplishing this, it all comes down to how you want it to appear to the user -- WHAT you want to have happen.

Categories