I'm writing a script that lets a user know how much time he spent on a page before refreshing it. To this purpose, I increment a time counter with a setInterval function, and store the data in the browser thanks to localStorage. Once the page is refreshed, I retrieve the data stored, and display them. Meanwhile, the time counter goes back to 0 and starts incrementing again.
Unfortunately, something is wrong with my script because localStorage doesn't store the updated time value (it's always -1). What's wrong with my script?
//TimeSpent = -1, so setInterval sets it to 0s instead of 1s when the page opens.
var timeSpent = -1
//Update time every second
var timer = setInterval(()=>{
timeSpent +=1;
}, 1000);
//If first visit, ask to refresh. Else, display timeSpent on previous page by retrieving localStorage data.
function start(){
if (localStorage.timeData){
var timerJson = localStorage.getItem("timeData");
var timerParsed = JSON.parse(timerJson);
console.log(`You spent ${timerParsed.time} seconds on previous page`)
}
else{
console.log("Reload the page and see how much time you spent reading this.")
}
}
//Trig function when page opens.
window.onload = start();
//Before page reloads, store timeSpent in localStorage as a Json file.
var timeData = {
time: timeSpent,
}
function storeData (timeData){
var timerJson = JSON.stringify(timeData)
localStorage.setItem("timeData", timerJson);
}
window.onbeforeunload = storeData (timeData)
Thanks!
window.onbeforeunload must have a value of type function but in your code it is undefined. Hence you should change it to this:
window.onbeforeunload = function storeData (){
var timerJson = JSON.stringify(timeData)
localStorage.setItem("timeData", timerJson);
}
I've also removed the parameter from the function, making it a closure.
UPD. As Jonas Wilms noted, you should do the same wilth onload event and start function.
ALSO. In order to always have the actual (fresh) value of timeSpent, you should do this:
const state = {timeSpent: -1}
And everywhere replace timeSpent with state.timeSpent.
This way the closures will have a link to state object, instead of just taking the initial value of a primitive timeSpent.
This code works well for me:
let timeData = {
time: -1
}
timeData.time = setInterval(()=>{
timeData.time += 1
console.log(timeData.time)
}, 1000);
function start(){
if (localStorage.timeData){
var timerJson = localStorage.getItem("timeData");
var timerParsed = JSON.parse(timerJson);
console.log(`You spent ${timerParsed.time} seconds on previous page`)
}
else{
console.log("Reload the page and see how much time you spent reading this.")
}
}
window.onload = start();
window.onbeforeunload = function () {
var timerJson = JSON.stringify(timeData)
localStorage.setItem("timeData", timerJson);
}
I assume you testing this locally. Since local-storage is stored like a cookie domain based (and you dont have a domain when you test your script locally) the data is simply not saved.
In HTML5, is the localStorage object isolated per page/domain?
Edit: By local i mean a simple Html-File without using a webserver.
I am wondering how i can solve the following issue. I have a Record in the Firebase DB which i am monitoring. The App is a Sports Score So far so good.
When user loads the initial page i check if Game is Running or Stopped and so a few things.
below is a snipped of what i do
if(Clock.Status=='Running'){
......
}
else if(Clock.Status == 'Stopped'){
.......
}
So far so good when user hits the page for first time. But now i want to monitor if the ClockStatus changes
clockStatusRef = firebase.database().ref("games/"+gameId+"/Score/");
clockStatusRef.on("child_changed", function(snapshot) {
var Clock = snapshot.val();
var status = Clock.Status;
// clock stopped - second scenario
if(status=='Stopped'){
stopTimer();
}
else if(status == 'Running'){
// clock status running- third scenario
firebase.database().ref('/.info/serverTimeOffset')
.once('value')
.then(function stv(data) {
console.log('hi');
serverTime = (data.val() + Date.now())/1000;
var timeElapsed = serverTime - Clock.ClockStart;
var totalCounts = document.getElementById("total_counts");
if(Clock.Timer > timeElapsed){
initTimer(Math.floor(Clock.Timer- timeElapsed),60);
}
else{
var Current_Clock = document.getElementById("count");
Current_Clock.innerHTML = '00:00';
}
}, function (err) {
return err;
});
}
console.log("Clock status changed: "+status);
});
for some strange reason on a change of status it starts with the main if
if(Clock.Status=='Running')
So i am wondering what am i missing and what is the best way to fix this so the first if is only run on the initial load and all subsequent will use the if's which handle status change of clock.
Here is the Json for games/B8120ACD-DF51-A64A-A83E-556007522E80/Score/Clock
{
"ClockStart" : 1510535632,
"Period" : 1,
"Status" : "Stopped",
"Timer" : 900
}
You're listening one level higher in your JSON than your code expects.
Either change the code that gets the clock from the snapshot to:
var Clock = snapshot.val().Clock;
Or (better, because it requires less data transfer) listen one level lower in the tree:
clockStatusRef = firebase.database().ref("games/"+gameId+"/Score/Clock");
As there seem to be some limitation as far as what is triggered when multiple listeners looking for changes and data overlaps i changed my code. As my App does not have any heavy traffic so changes are not that often, i use one listener for changes and to address my issue i just went ahead and added the run once for the initial setup then run different code on updates. Would have been nice to control what listener gets the notification of change, also the child_changed seems to have its limitations as i got it to fire but was not able to tell which child actually changed.
I have a date input in my page, which I'm using Daterangepicker framework to populate it.
Here is the code of how I start my page!
$(function(){
startSelectors();
var variaveis = returnInputVars();
var rede = variaveis[0];
var codLoja = variaveis[1];
var period = variaveis[2];
console.log('1.'+rede+' 2.'+codLoja+' 3.'+period);
});
function returnInputVars(){
var rede = $("#dropdown-parceria").val();
var codLoja = $("#dropdown-loja").val();
var periodo = $("#datepicker-range").val();
return [rede, codLoja, periodo];
};
The function startSelectors() is set to start my datepicker and other fields, which is working perfectly. After it, I create a var called "variaveis" to fill
with the values of each field because I will use then later (this functions also works perfectly at other scripts of my page).
Running the page, my console returns this:
The funny thing is, if I type at the console this, the value is shown, just while starting the script is does not work!
Anybody experienced something like this?
***UPDATE
Adding this script to my start function:
console.log($("#datepicker-range"));
The value is shown, but the second console.log don't:
EDIT 1. FIDDLE (Suggested by #halleron)
To ensure things are loaded in the correct order, it is useful to apply a page sniffer code snippet that will scan the page continuously until a condition is met, or until a preset counter limit is reached (to prevent strain on browser memory). Below is an example of what I typically use that would fit your scenario.
I think because you are dealing with asynchronous loading, you can't have a global variable that holds the values in a global scope without an interval to detect when it can be used. Otherwise, it will attempt to read the variable when it is not yet ready.
You can invoke functions anywhere you like. But I would keep all of your variables contained within the page_sniffer_2017() because that is a controlled environment where you know that everything successfully loaded and you know that the variables are ready to be accessed without error.
That way, regardless of connection speed, your functions will only fire when ready and your code will flow, sequentially, in the right order.
Within the ajax success options, always add a class to the body of the document that you can search on to determine if it has finished loading.
$(document).ready(function() {
page_sniffer_2017();
});
function page_sniffer_2017() {
var counter = 0;
var imgScanner = setInterval(function() {
if ($("#datepicker-range").length > 0 && $("#datepicker-range").val().length && jQuery('body').hasClass('date-picker-successfully-generated')) {
var periodoDatepicker = $("#datepicker-range").val(); // ok
console.log(periodoDatepicker); // ok
var variaveis = returnInputVars(replaceDate(periodoDatepicker)); // ok
console.log(variaveis[0], variaveis[1], variaveis[2]);
//startNewSelectors(variaveis);
// start ajax call
generateData(variaveis[0], variaveis[1], variaveis[2]);
clearInterval(imgScanner);
} else {
//var doNothing = "";
counter++;
if (counter === 100) {
console.log(counter);
clearInterval(imgScanner);
}
}
}, 50);
}
Using jQuery I'm writing a website api call in Javascript, which so far works pretty well. When a person updates a number in a text input it does a call to the API and updates a field with the response. It gets problematic however, when I user quickly makes a lot of changes. The javascript then seems to pile up all queries, and somehow does them side by side, which gives the field to be updated kind of a stressy look.
I think one way of giving the user a more relaxed interface, is to only start the API call after the user finished editing the input field for more than half a second ago. I can of course set a timeout, but after the timeout I need to check if there is not already a call under way. If there is, it would need to be stopped/killed/disregarded, and then simply start the new call.
First of all, does this seem like a logical way of doing it? Next, how do I check if a call is underway? And lastly, how do I stop/kill/disregard the call that is busy?
All tips are welcome!
[EDIT]
As requested, here some of the code I already have:
function updateSellAmount() {
$("#sellAmount").addClass('loadgif');
fieldToBeUpdated = 'sellAmount';
var buyAmount = $("#buyAmount").val();
var sellCurrency = $("#sellCurrency").val();
var buyCurrency = $("#buyCurrency").val();
var quoteURL = "/api/getQuote/?sellCurrency="+sellCurrency
+"&buyAmount="+buyAmount
+"&buyCurrency="+buyCurrency;
$.get(quoteURL, function(data, textStatus, jqXHR){
if (textStatus == "success") {
$("#sellAmount").val(data);
$("#sellAmount").removeClass('loadgif');
}
});
if (fieldToBeUpdated == 'sellAmount') {
setTimeout(updatesellAmount, 10000);
}
}
$("#buyAmount").on("change keyup paste", function(){
updateSellAmount();
});
If you make your AJAX call like this:
var myAjaxDeferred = $.ajax("....");
You can check it later with:
if (myAjaxDeferred.state() === "pending") {
// this call is still working...
}
This might be a newb question but....
Recently, I have been using window.setTimeout which makes a recursive call to the parent function, which makes an ajax poll to the server.
function connectToVM(portal) {
//making the ajax call here....
if(response.responseText !== "")
{
windowInterval = window.setTimeout(function() {
connectToVM(portal)
}
, 4000);
}
}
windowInterval is my global var here.
if(!checkIfChartExists()) {
window.clearInterval(windowInterval);
}
Now, instead of making use of variables here, I know that I can simple pass the function to clearTimeout, but that also causes all the other intervals to be stopped :(
The reason why I am doing this is the server does a timeout, only when there is a response.
My scenario is, I have a chart which updates every timeout interval.
AFAIK, when we set the interval, there is a specific value set to the variable(if set to a variable). So when I print my variable(every time when the timeout function is called), I get some int values which are unique.
I have many tabs, and many tabs can have same chart.. which just makes use of the same interval which is triggered earlier.
Now I have just 1 chart.. But I have many charts to show which are of the same type. Say gauge Chart.
I also have to clear the timeout whenever there is no chart present in the current selected tab - which I am doing.
So I am planning to make just 1 function which just makes the call to the server by passing in the required params to this function.
But in order to poll, I am using window.setTimeout thing I mentioned above.
This works for 1 chart.
Now, I try to add 1 more chart, with different set of params to poll the server, I will need to make use of some different setTimeout function, which has a id different than that of the earlier triggered timeout.
I also have to consider that if the 1st chart is already present, the timeout is already triggered and have to keep it running.
So, now I have to trigger the second timeout.
But there is no second timeout here.
I was wondering if there is any alternate approach to this, as I can't really predict how many chart's there will be on runtime.
Question 1 : Can we flood our browser with many timeout's?
Question 2 : How to get the id of that particular timeout, so that I can clearTimeout(id) on it?
Question 3 : Since we can't assign / make variables on the fly, how to set / make such a data structure which can hold such a pointer to the particular chart's index / id.. so that we can easily get hold of it and clear it.
Question 4 : Is this the only way we can poll the server(via AJAX) if we have to poll continually?
Lastly, I recognize this is a very complex issue I have posted in here. But I am sure I will find some useful information about the approach from the forums.
I don't have much experience doing all these stuffs in JS, but any help is appreciated!
Update
Sorry I have to post my code in here.. But I am using Extjs to get my chart portlets. My code for the function connectToVM is this :
function connectToVM(portalId, host, port, user, passwd, db) {
try{
if(Ext.getCmp(portalId))
{
var gaugeChartForTitle = Ext.getCmp(portalId);
if(typeof portalOriginalTitle === 'undefined')
portalOriginalTitle = gaugeChartForTitle.title;
var gaugeChartDiv = document.getElementById(portalId);
Ext.Ajax.request({
url: "/connectToVM?" +
Ext.urlEncode({
host: host,
port: port,
user: user,
passwd: passwd,
db: db
}),
method: 'GET',
success: function (response, options) {
if(response.responseText !== "")
{
gaugeChartDiv.style.background = "";
gaugeChartForTitle.setTitle(portalOriginalTitle);
console.log("Virtual Machine at "+ host +" : BUSY % : "+response.responseText);
virtualMachineStore.loadData(generateVirtualMachineData(response.responseText)); //Setting the data1 value of the store and loading it for display!
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 4000);
}
else
{
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 10000); //Retry every 10 seconds to check if the connection is established!
gaugeChartDiv.style.background = "red";
gaugeChartForTitle.setTitle(portalOriginalTitle +" - Connection Failure. Reconnecting!");
}
},
failure: function ( result, request) {
}
});
}
}
catch(err) {
}
}
Now, I trigger my function using this :
function setWindowIntervalForVM(portalId) {
//console.log("isIntervalActivated inside setWindowIntervalForVM() : "+isIntervalActivated);
if(!isIntervalActivated) {
connectToVM(portalId, host, port, user, pwd, db);
}
}
function checkIfWindowIntervalIsActivated(portal) {
if(!isIntervalActivated) {
setWindowIntervalForVM(portal.id);
isIntervalActivated = true;
} else {
window.clearInterval(windowInterval);
windowInterval = null;
isIntervalActivated = false;
}
}
So checkIfWindowIntervalIsActivated() is my parent function call which I call in these scenarios :
1) Whenever the Gauge Chart is newly created.. I Trigger this call and have a boolean isIntervalActivated which if it is false, triggers the server poll.
2) So now if I have the chart already in tab 1(since the user selected it), I now change to tab 2 which does not have it. So I simply set isIntervalActivated to true which stops the poll. This is handled for 1 chart. Now the question here is, if I want to make this function re-usable, say I want to drop one more chart of same type but with different server parameters to poll, how to make use of the same windowInterval variable which has my 1st chart's triggered timeout value. P.S: The value changes for every ajax request it makes. So there'z no 1 single value :(
3) I stop the poll whenever there is no chart of same type present.. in other tab. which makes perfect sense. Now, I am caching all my portlets whenever user drops in a new portlet / on the page load, pulling all the user configured portlets. In such a case, I have to trigger all of the charts' ajax calls.. each polling to its configured destination. Now, I do not know how many charts there will be, as in my function name, I am polling to VM's. So if the user consumes VM1, it switches to VM2 and so on.
So it's absolutely impossible to just create same function for many such similar charts.
So just wanted to check if I can re-use the same timeOut thing, or take a totally different approach to this problem :( :(
I hope it's a bit clear now, if not I can explain my situation more.
Please ask me more questions if required :)
Thanks again!
If I understood correctly and you're trying to support multiple charts updating concurrently, I'd switch from keeping the chart data inside the connectToVM() closure to an explicit array of chart objects and use a single interval to update all charts.
Something like the following (treat it as pseudo-code):
var charts = [
// an array of chart objects, see addChart()
];
function addChart() {
// when you need to add or remove a chart, update the charts object, like this:
charts.push({
update: updateChart,
nextUpdateTime: null, // or Date.now() if you don't care about old browsers.
chartData: {host: ..., port: ..., user: ..., passwd: ..., db: ...,
originalTitle: ..., portalId: ...},
});
restartUpdates();
}
var activeInterval = null;
function restartUpdates() {
if (activeInterval) {
clearInterval(activeInterval);
}
activeInterval = setInterval(updateCharts, 5000);
}
// updates all active charts
function updateCharts() {
var now = new Date().getTime();
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
if (chart.nextUpdateTime !== null && chart.nextUpdateTime < now) {
chart.nextUpdateTime = null; // chart.update() will re-set this
try {
chart.update(chart);
} catch(e) {
// handle the error
}
}
}
// update a single chart.
// #param |chart| is an item from the |charts| array.
function updateChart(chart) {
// ...same as your connectToVM() using properties from chart.chartData...
Ext.Ajax.request(
// ...
success: function (response, options) {
// ...same as before...
// ...but instead of re-setting the timeout:
// windowInterval = window.setTimeout(function() {
// connectToVM(portalId, host, port, user, passwd, db)
// }
// , 4000);
// ...update the chart's nextUpdateTime:
chart.nextUpdateTime = (new Date().getTime()) + 4000;
}
);
}
initial answer below
Thanks for the detailed question! It feels you're missing something very obvious wrt questions #2/3, but it's hard to tell what specifically without seeing more of your code. Can you post a more complete, yes simple example demonstrating the problem you're trying to solve? Perhaps the function handling changing the active tab in pseudocode would help, like this:
function selectTab(tabID) {
// ...activate tab #tabID in the GUI...
if (tabID == 1) {
// there's chart #1 on tab #1, need to stop any active timeouts and start a new one
connectToVM("chart #1");
} else if (tabID == 2) {
// no charts on tab #2.. need to stop any active timeouts
} else if (tabID == 3) {
// ...
}
}
One thing I don't understand is whether there's always a single chart, that needs updating, at any point of time?
Also, do you know the concepts mentioned in A re-introduction to JavaScript, specifically objects?
As for the questions:
1: yes, too many timeouts should be avoided (thousands a second will probably make the CPU hot and the browser sluggish), although I'd be more worried about the server, which has to handle the requests from multiple clients.
2/3: see above.
4: The Comet page lists a lot of alternatives to basic AJAX polling (server-sent events, long-polling, websockets), but I'd worry about this later.
Yes
var x = window.setTimeout(...); window.clearTimeout(x);
Store it as a data attribute on the active tab, a property on your object, or as a global variable. Many different ways. Example code would have made it easier to answer this.
Based on your comments:
var windowInterval;
function connectToVM(portal) {
if(windowInterval)window.clearTimeout(windowInterval);
windowInterval = window.setTimeout(function() { ... }, 4000);
}