I am trying to perform some JS math to add a total value, based upon a series of variables which obtain their values from a JSON object. However when I run the script in the browser, the console log shows me almost half of the sum done and half just chained together as a string.
If I paste the following into the console:
var sparksOfferCount = 10;
var sparksAddedOffers = 3;
var sparksUNaddedOffers = (sparksOfferCount - sparksAddedOffers); // offers that haven't been added yet
var sparksAllRewards = 0;
var sparkstotalStreakOffers = 0;
var derivedSparksTotalOffers = (sparksUNaddedOffers + sparksAllRewards + sparkstotalStreakOffers);
console.log('derived total of new offers', derivedSparksTotalOffers);
It will return 7. However, above I have replaced references to the JSON object with actual integers. When I run my code like this where it takes it's values dynamically from the JSON call, it returns '70' in the console. Bizarre, because it means that it is able to subtract 3 from 10 and then add one 0 for sparksAllRewards, but it doesn't show another 0 for sparkstotalStreakOffers.
var sparksSSO = JSON.parse(sessionStorage.getItem('sparksSSO')) || {};
// global variables for use with session storage
var sparksOfferCount = (sparksSSO.totalOffers >= 0) ? sparksSSO.totalOffers : "" ;
var sparksAllOffers = (sparksSSO.allOffers);
var sparksAddedOffers = (sparksSSO.totalAddedOffers);
var sparksUNaddedOffers = (sparksOfferCount - sparksAddedOffers); // offers that haven't been added yet
var sparksAllRewards = (sparksSSO.allRewards);
var sparkstotalStreakOffers = (sparksSSO.totalStreakOffers);
var derivedSparksTotalOffers = (sparksUNaddedOffers + sparksAllRewards + sparkstotalStreakOffers);
console.log('derived total of new offers from session storage', derivedSparksTotalOffers);
I'm rather baffled. Any ideas?
EDIT:
Here is the working script, as per the guidance of Arthur Borba
var sparksOfferCount = (sparksSSO.totalOffers >= 0) ? sparksSSO.totalOffers : 0 ;
var sparksAllOffers = Number(sparksSSO.allOffers);
var sparksAddedOffers = Number(sparksSSO.totalAddedOffers);
var sparksUNaddedOffers = Number(sparksOfferCount - sparksAddedOffers); // offers that haven't been added yet
var sparksAllRewards = Number(sparksSSO.allRewards);
var sparkstotalStreakOffers = Number(sparksSSO.totalStreakOffers);
var derivedSparksTotalOffers = Number(sparksUNaddedOffers + sparksAllRewards + sparkstotalStreakOffers);
console.log('derived total of new offers from session storage', derivedSparksTotalOffers);
Try initializing your counter
var sparksOfferCount = (sparksSSO.totalOffers >= 0) ? sparksSSO.totalOffers : "" ;
with a zero (not an empty string), like this:
var sparksOfferCount = (sparksSSO.totalOffers >= 0) ? sparksSSO.totalOffers : 0 ;
And also check this Number so you ensure you are working with numbers and not strings:
var sparksAllOffers = Number(sparksSSO.allOffers); // do it for every variable
If that's not quite what you want, please provide some example of your JSON data.
Related
Follow up to Sending duplicate data to array, then checking if said array is empty or not to decide what code to run next, if statement not working correctly.
I'm pretty much trying to copy the conditional formatting that I put to notify users of "bad" data, but with script to prevent sending bad data and giving unique error messages. With the answer from my last question, the checks and preventing sending no data or data with duplicates works, but I'm now stuck on preventing sending of data that does not match the format of a four digit number.
My conditional formatting was to set the background color to orange on anything that was not equal to or in between 1000 and 9999, but I'm having trouble getting the scripting to work--I'm trying to use negation of anything in between those two values as the "false" that will prompt an error message, and the "true" to let the rest of the script run that will send the data and notification emails out. However, this makes it say that there are bad values even if I do have the correct data in. Removing the negation lets anything go through, like it's not actually checking it. Any ideas?
The section I'm asking about is the last else if statement:
else if (!data.every(function(num) {return num >= 1000 && num <= 9999})) {
SpreadsheetApp.getUi().alert("You have incorrectly formatted tallies, tallies must be four digits.", SpreadsheetApp.getUi().ButtonSet.OK);
}
Total code is below.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssSheet = ss.getActiveSheet();
//https://techyesplease.com/code/google-apps-script-find-duplicates-sheets/
function readData() {
var dataColumn = 3;
var firstRow = 6;
var lastRow = ssSheet.getLastRow();
var numRows = lastRow - firstRow + 1;
var columnRange = ssSheet.getRange(firstRow, dataColumn, numRows);
//var rangeArray = columnRange.getValues();
// Convert to one dimensional array
//rangeArray = [].concat.apply([], rangeArray);
var rangeArray = columnRange.getValues().flat().filter(String);
return rangeArray;
}
// Sort data and find duplicates
function findDuplicates(dataAll) {
var sortedData = dataAll.slice().sort();
var duplicates = [];
for (var i = 0; i < sortedData.length - 1; i++) {
if (sortedData[i + 1] == sortedData[i]) {
duplicates.push(sortedData[i]);
}
}
return duplicates;
}
//Use the same string for this variable as the name for the requestor for his or her column of data. E.g., John Doe
//****GLOBALS****
var targetSheet = 'All Tallies'; //replace with sheet/tab name
var targetSpreadsheetID = 'id' //replace with destination ID
var targetURL = 'url'
//var dataNotificationReceivingEmailAddresses =
//Set up to be able to easily change what emails the data notification goes to?
function sendDataAndTimestamp2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssSheet = ss.getActiveSheet();
var sourceRange = ssSheet.getRange('C6:C401');
//assign the range you want to copy, could make C6:C? No, would like dynamic.
var data = sourceRange.getValues();
var nameRange = ssSheet.getRange('C4:D4');
var nameValue = nameRange.getDisplayValue();
var tallyDateRange = ssSheet.getRange('C2');
var tallyDateValue = tallyDateRange.getDisplayValue();
var tallyDateText = 'Tallies to run on '+ tallyDateValue;
var tallyAmountRange = ssSheet.getRange(8,1);
var tallyAmount = tallyAmountRange.getDisplayValue();
var tallyAmountNumberOnly = data.filter(String).length;
//Used as tallyAmount includes text, for some cases need the number only
var thisDocumentUrl = ss.getUrl();
//Variables for the sending/source spreadsheet above
//Initial confirmation alert, need checks for blank or error tallies first. First condition needs to loop through data variable and check that if any values are numbers not between 1000 and 9999, throw up Ui alert error message. Second condition goes to result variable?
//Reference the earlier functions
var dataArray = readData();
var duplicates = findDuplicates(dataArray);
//Need to check data and have error message if duplicates.length >=1, if 0 allow, refuse if data length less than 1
if (duplicates.length !== 0) {
SpreadsheetApp.getUi().alert("Your tallies include duplicates, please remove them then try again.", SpreadsheetApp.getUi().ButtonSet.OK);
Logger.log(duplicates);
}
else if (dataArray.length ===0) {
SpreadsheetApp.getUi().alert("You have not input any tallies.", SpreadsheetApp.getUi().ButtonSet.OK);
}
else if (!data.every(function(num) {return num >= 1000 && num <= 9999})) {
SpreadsheetApp.getUi().alert("You have incorrectly formatted tallies, tallies must be four digits.", SpreadsheetApp.getUi().ButtonSet.OK);
}
/*https://www.youtube.com/watch?v=gaC290XzPX4&list=PLv9Pf9aNgemvD9NFa86_udt-NWh37efmD&index=10
Every method
var arr = [1,2,3,4];
var allTalliesGood = data.every(function(num){
return num < 9
});
//Use with num >= 1000, num <=9999, and then if true continue, if false stop and give error msg
*/
/*
dataFiltered = data.filter(filterlogic);
var filterlogic = function(tally){
if (tally >= 1000 && tally <= 9999){
return true;
} else {
return false
}
}
//if use false for good tallies, and rename dataFiltered to something like "dataBad", then check if dataBad has true matches in it...
//need to check for strings? what happens if letters/symbols in tally range?
//var dataBad = data.filter(function(tally){ return tally < 1000 || tally > 9999;});
// use OR (||) for getting tallies great than or less than, what about letters/symbols? Use NOT good range?
//https://www.youtube.com/watch?v=hPCIOohF0Fg&list=PLv9Pf9aNgemvD9NFa86_udt-NWh37efmD&index=8
//sort method link above
*/
else {
// rest of code
var result = SpreadsheetApp.getUi().alert("You're about to notify scheduler of the following number of tallies: " + tallyAmountNumberOnly, SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(result === SpreadsheetApp.getUi().Button.OK) {
//Code to send out emails and data
As far as I can tell in the function sendDataAndTimestamp2() you need to get rid of empty values from data. It can be done with filter(String) method:
var data = sourceRange.getValues().filter(String); // <-- here
. . .
else if (!data.every(function(num) {return num >= 1000 && num <= 9999}))
. . .
Or, I don't know, perhaps you meant dataArray instead of data:
else if (!dataArray.every(function(num) {return num >= 1000 && num <= 9999}))
Btw, the line can be shortened a bit:
else if (!data.every(x => (x >= 1000) && (x <= 9999)))
I've written a JavaScript loop that is supposed to get 8 arrays and store them in localStorage in a specific order. It works, however the order is a bit messed up: the values are all "shifted down" one key in localStorage, ie the last value is being put into the first key, the first value is being put into the second key, and so on. I have tried both a for loop and a "self-built" loop, which both gave the same results. Here is the code, I will do more explaining at the bottom.
var i = -1;
function getstats() {
i = i + 1;
if (i > -1 && i < 9) {
var ids = document.getElementById("battle_deck_block_" + i).getElementsByTagName("img")[0].id;
var tribe;
if (ids < 117603 && ids > 100000) {
tribe = "Xana";
} else if (ids < 213403 && ids > 200000) {
tribe = "Hemi";
} else {
tribe = "Theri";
}
var referenceurl = "Dot_files/Trade/" + tribe + "/" + ids + ".js";
var temp = document.createElement("SCRIPT");
var src = document.createAttribute("src");
src.value = referenceurl;
temp.setAttributeNode(src);
document.head.appendChild(temp);
var hp = sessionStorage.getItem("minhp");
var atk = sessionStorage.getItem("minatk");
var def = sessionStorage.getItem("mindef");
var wis = sessionStorage.getItem("minwis");
var agi = sessionStorage.getItem("minagi");
var stats = [hp, atk, def, wis, agi];
localStorage.setItem("fighter" + i + "_stats", JSON.stringify(stats));
document.head.removeChild(temp);
} else if (i > 8 || i < 0) {
return;
};
};
setInterval(getstats, 200);
So basically this is a function that fetches characters' stats for a battle system I am working on for a game.
The function builds a reference URL based on the character's id, and sets it as the src for a script tag that is also created in the function (this tag is later deleted and recreated for each loop).
Each source file used in the script tag is a JavaScript file that sets the character's stats in sessionStorage values. These values are then put into the respective variable and then the variables are put into an array and stored in localStorage to be used later.
Now, as I mentioned earlier this all works fine and dandy, except for the order of the localStorage values. This is where I am looking for help.
Can someone go over my code and come up with a solution that can fix the order of the values in localStorage? I can post more codes/explanations as needed.
Cheers.
~ DM
As Pointy mentioned, your issue is that the code doesn't pause to wait for the fetched script to be downloaded and executed before running the localStorage code. That means the first time this script will run, the sessionStorage values will be empty, the first loop would be empty, the second loop would have the values from the first script, and so on. When you run the script a second time, the last values from the previous run are in sessionStorage, thus the first key gets the last values, etc.
You can fix this by (1) Using the onload event of the script tag, and (2) using closures to freeze the state of i for your script (since it will be mutated as the loop continued).
var i = -1;
function getClosure(i, temp) {
// Inside this closure, the current values of i and temp are preserved.
return function() {
var hp = sessionStorage.getItem("minhp");
var atk = sessionStorage.getItem("minatk");
var def = sessionStorage.getItem("mindef");
var wis = sessionStorage.getItem("minwis");
var agi = sessionStorage.getItem("minagi");
var stats = [hp, atk, def, wis, agi];
localStorage.setItem("fighter" + i + "_stats", JSON.stringify(stats));
document.head.removeChild(temp);
}
}
function getstats() {
i = i + 1;
if (i > -1 && i < 9) {
var ids = document.getElementById("battle_deck_block_" + i).getElementsByTagName("img")[0].id;
var tribe;
if (ids < 117603 && ids > 100000) {
tribe = "Xana";
} else if (ids < 213403 && ids > 200000) {
tribe = "Hemi";
} else {
tribe = "Theri";
}
var referenceurl = "Dot_files/Trade/" + tribe + "/" + ids + ".js";
var temp = document.createElement("SCRIPT");
var src = document.createAttribute("src");
src.value = referenceurl;
temp.setAttributeNode(src);
// The function returned by getClosure() will be executed after this script loads
temp.onload = getClosure(i, temp);
document.head.appendChild(temp);
} else if (i > 8 || i < 0) {
return;
};
};
setInterval(getstats, 200);
Looking to extend my javascript object, I want to find the minium and maximum of a multicolumn csvfile. I have looked up solutions but I cannot really grasp the right way. I found a solution here: Min and max in multidimensional array but I do not get an output.
My code that I have for now is here:
function import(filename)
{
var f = new File(filename);
var csv = [];
var x = 0;
if (f.open) {
var str = f.readline(); //Skips first line.
while (f.position < f.eof) {
var str = f.readline();
csv.push(str);
}
f.close();
} else {
error("couldn't find the file ("+ filename +")\n");
}
for (var i=(csv.length-1); i>=0; i--) {
var str = csv.join("\n");
var a = csv[i].split(","); // convert strings to array (elements are delimited by a coma)
var date = Date.parse(a[0]);
var newdate = parseFloat(date);
var open = parseFloat(a[1]);
var high = parseFloat(a[2]);
var low = parseFloat(a[3]);
var close = parseFloat(a[4]);
var volume = parseFloat(a[5]);
var volume1000 = volume /= 1000;
var adjusted_close = parseFloat(a[6]);
outlet(0, x++, newdate,open,high,low,close,volume1000,adjusted_close); // store in the coll
}
}
Edit
What if, instead of an array of arrays, you use an array of objects? This assumes you're using underscore.
var outlet=[];
var outletkeys=['newdate','open','high','low','close','volume','volume1000','adjusted_close'];
for (var i=(csv.length-1);i>0; i--) {
var a = csv[i].split(",");
var date = Date.parse(a[0]);
var volume = parseFloat(a[5],10);
outlet.push( _.object(outletkeys, [parseFloat(date,10) , parseFloat(a[1],10) , parseFloat(a[2],10) , parseFloat(a[3],10) , parseFloat(a[4],10) , parseFloat(a[5],10) , volume /= 1000 , parseFloat(a[6],10) ]) );
}
Then the array of the column 'open' would be
_.pluck(outlet,'open');
And the minimum it
_.min(_.pluck(outlet,'open'));
Edit2
Let's forget about underscore for now. I believe you need to get the maximum value on the second column, which is what you put in your open variable.
¿Would it help if you could have that value right after the for loop? For example
var maxopen=0;
for (var i=(csv.length-1); i>=0; i--) {
var a = csv[i].split(",");
var date = Date.parse(a[0]);
var newdate = parseFloat(date);
var open = parseFloat(a[1]);
maxopen=(open>maxopen)? open : maxopen; // open overwrites the max if it greater
...
...
outlet(0, x++, newdate,open,high,low,close,volume1000,adjusted_close);
}
console.log('Maximum of open is',maxopen);
My problem is I am trying to extract certain things from the url. I am currently using
window.location.href.substr()
to grab something like "/localhost:123/list/chart=2/view=1"
What i have now, is using the index positioning to grab the chart and view value.
var chart = window.location.href.substr(-8);
var view = window.location.href.substr(-1);
But the problem comes in with I have 10 or more charts. The positioning is messed up. Is there a way where you can ask the code to get the string between "chart=" and the closest "/"?
var str = "/localhost:123/list/chart=2/view=1";
var data = str.match(/\/chart=([0-9]+)\/view=([0-9]+)/);
var chart = data[1];
var view = data[2];
Of course you may want to add in some validation checks before using the outcome of the match.
Inspired by Paul S. I have written a function version of my answer:
function getPathVal(name)
{
var path = window.location.pathname;
var regx = new RegExp('(?:/|&|\\?)'+name+'='+'([^/&,]+)');
var data = path.match(regx);
return data[1] || null;
}
getPathVal('chart');//2
Function should work for fetching params from standard get parameter syntax in a URI, or the syntax in your example URI
Here's a way using String.prototype.indexOf
function getPathVar(key) {
var str = window.location.pathname,
i = str.indexOf('/' + key + '=') + key.length + 2,
j = str.indexOf('/', i);
if (i === key.length + 1) return '';
return str.slice(i, j);
}
// assuming current path as described in question
getPathVar('chart');
You could split your string up, with "/" as delimiter and then loop through the resulting array to find the desired parameters. That way you can easily extract all parameters automatically:
var x = "/localhost:123/list/chart=2/view=1";
var res = {};
var spl = x.split("/");
for (var i = 0; i < spl.length; i++) {
var part = spl[i];
var index = part.indexOf("=");
if (index > 0) {
res[part.substring(0, index)] = part.substring(index + 1);
}
}
console.log(res);
// res = { chart: 2, view: 1}
FIDDLE
I have some google spreadsheet logbook where I store duration of some activities in hours format [[HH]:MM:SS]. The spreadsheet adds such cells with no issues. However when I try to add them via Google Script I get some garbage. What I found is that Date() object is implicitly created for such cells, but I cannot find API of that value type.
I know I can convert the data to "hour integers" by multiplying them by 24 but that is a nasty workaround as it demands duplication of many cells. I would rather like a solution that will allow to do that in google script itself.
here is a working function that does the trick.
I first tried to format it as a date but 36 hours is not really standard !! so I did a little bit of math :-) )
To get it working you should set a cell somewhere with value 00:00:00 that we will use as a reference date in spreadsheet standard. in my code it is cell D1(see comment in code, reference date in SS is in 1900 and in Javascript is in 1970 ... that's why it is a negative constant of 70 years in milliseconds...)
here is the code and below a screen capture of the test sheet + the logger
It would be a good idea to modify this code to make it a function that takes cell value as parameter and returns the result as an array for example ([h,m,s] or something similar), this code is only to show how it works.
function addHoursValues() {
var sh = SpreadsheetApp.getActive()
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
var ref = sh.getRange('D1').getValue().getTime();
//var ref = -2209161600000 // you could also use this but it would be less obvious what it really does ;-)
Logger.log(ref+' = ref');
var h1 = parseInt((hours1.getTime()/3600000)-ref/3600000);
var h2 = parseInt((hours2.getTime()/3600000)-ref/3600000);
Logger.log(h1+' + '+h2+' = '+(h1+h2))
var m1 = parseInt((hours1.getTime()-h1*3600000-ref)/60000);
var m2 = parseInt((hours2.getTime()-h2*3600000-ref)/60000);
Logger.log(m1+' + '+m2+' = '+(m1+m2))
var s1 = parseInt((hours1.getTime()-h1*3600000-m1*60000-ref)/1000);
var s2 = parseInt((hours2.getTime()-h2*3600000-m2*60000-ref)/1000);
Logger.log(s1+' + '+s2+' = '+(s1+s2))
var ts=s1+s2
var tm=m1+m2
var th=h1+h2
if(ts>59){ts=ts-60;tm++};
if(tm>59){tm=tm-60;th++}
Logger.log('sum = '+th+':'+tm+':'+ts)
}
EDIT : here are 2 "function" versions with corresponding test functions that show how to use it
function getHMS(hrs) {
var t = hrs.getTime()/1000;
var ref = -2209161600;
var h = parseInt((t-ref)/3600);
var m = parseInt((t-h*3600-ref)/60);
var s = parseInt(t-h*3600-m*60-ref);
return[h,m,s];// returns an array of 3 discrete values
}
function testHMS(){
var sh = SpreadsheetApp.getActive();
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
var sumS = getHMS(hours1)[2]+getHMS(hours2)[2];// add seconds
var sumM = getHMS(hours1)[1]+getHMS(hours2)[1];// add minutes
var sumH = getHMS(hours1)[0]+getHMS(hours2)[0];// add hours
if(sumS>59){sumS=sumS-60 ; sumM++}; // handles values >59
if(sumM>59){sumM=sumM-60 ; sumH++}; // handles values >59
Logger.log(sumH+':'+sumM+':'+sumS);
}
OR
function addHMS(hrs1,hrs2) {
var t1 = hrs1.getTime()/1000;
var t2 = hrs2.getTime()/1000;
var ref = -2209161600;
var h = parseInt((t1-ref)/3600)+parseInt((t2-ref)/3600);
var m = parseInt((t1-parseInt((t1-ref)/3600)*3600-ref)/60)+parseInt((t2-parseInt((t2-ref)/3600)*3600-ref)/60);
var s = parseInt(t1-parseInt((t1-ref)/3600)*3600-parseInt((t1-parseInt((t1-ref)/3600)*3600-ref)/60)*60-ref)
+parseInt(t2-parseInt((t2-ref)/3600)*3600-parseInt((t2-parseInt((t2-ref)/3600)*3600-ref)/60)*60-ref);
if(s>59){s=s-60 ; m++}; // handles values >59
if(m>59){m=m-60 ; h++}; // handles values >59
return[h,m,s];// returns sum in an array of 3 discrete values
}
function othertestHMS(){
var sh = SpreadsheetApp.getActive();
var hours1 = sh.getRange('A1').getValue();
var hours2 = sh.getRange('B1').getValue();
Logger.log(addHMS(hours1,hours2));
}