Are Google's scrips always that slow? - javascript

I wrote a little script for Google Sheets. Whenever I add something new in the table, the new values are added. If I do this quickly (enter a value and press Enter directly so that I get to the next line) the script doesn't keep up and leaves out values (lines). Has anyone ever had this problem and knows how to solve it?
I'm not concerned with how it actually works, just how I can make these lines run faster in Google Sheets.
function addDateTimetoCell(e){
var pDate = new Date();
var pSheet = SpreadsheetApp.getActiveSheet();
var pRange = e.range;
switch(pRange.getColumn()){
case 1:{
if(pSheet.getRange(pRange.getRow(), 4).getValue().length <= 0){
pSheet.getRange(pRange.getRow(), 4).setValue(pDate);
if(pSheet.getRange(pRange.getRow(), 8).getValue() <= 0){
pSheet.getRange(pRange.getRow(), 8).setValue(pSheet.getRange(pRange.getRow(), pRange.getColumn()).getValue());
}
}
break;
}
case 2:{
if(pSheet.getRange(pRange.getRow(), 5).getValue().length <= 0){
pSheet.getRange(pRange.getRow(), 5).setValue(pDate);
if(pSheet.getRange(pRange.getRow(), 9).getValue() <= 0){
pSheet.getRange(pRange.getRow(), 9).setValue(pSheet.getRange(pRange.getRow(), pRange.getColumn()).getValue());
}
}
break;
}
case 3:{
if(pSheet.getRange(pRange.getRow(), 6).getValue().length <= 0){
pSheet.getRange(pRange.getRow(), 6).setValue(pDate);
if(pSheet.getRange(pRange.getRow(), 10).getValue() <= 0){
pSheet.getRange(pRange.getRow(), 10).setValue(pSheet.getRange(pRange.getRow(), pRange.getColumn()).getValue());
}
}
break;
}
}
}

Google Apps Script is pretty fast, but calls to the SpreadsheetApp API are very slow. You should minimize the number of calls to the API by assigning the calls to a variable especially if they are being called more than once.
I also think the code is quite not optimized. Repeating calls, unnecessary switch case usage (since there is a pattern with case vs column).
I assigned these duplicate calls to a variable and use those. Also, I did simplify the switch case to make it simpler. Test the code if it works like previously and faster.
Code:
function addDateTimetoCell(e){
var pDate = new Date();
var pSheet = SpreadsheetApp.getActiveSheet();
var pRange = e.range;
var row = pRange.getRow();
var column = pRange.getColumn();
var rangeCol1 = pSheet.getRange(row, column + 3);
if(rangeCol1.getValue().length <= 0){
rangeCol1.setValue(pDate);
var rangeCol2 = pSheet.getRange(row, column + 4);
if(rangeCol2.getValue() <= 0){
rangeCol2.setValue(rangeRC.getValue());
}
}
}

Related

JS create an array with unique random numbers

Full code looks like this, ideally we have 4 div boxes that need to be randomly filled with random numbers ansValue, one of them (rightAnsValue with its rightAnsId) is already done and works fine, I've managed to make it unique in comparison to others (code without commented section). But met a problem with making others unique, I keep having some identical values in my boxes. In comments is one way I tried to solve this, but pretty sure there is a much simpler and smarter solution that actually works. I would appreciate if you could help to find an understandable solution to this problem.
(P.S. I've seen similar questions but they are either too dificult or done without JS.)
function createAnswers(){
for(ansId=1; ansId<5; ansId++){
if(ansId!=rightAnsId){
for(i=1; i<10; i++){
digitArray[i-1] = i;
}
genNewRandNum();
// ansArray.length = 3;
// ansArray.push(ansValue);
// for(k=0; k<3; k++){
// if(ansArray[k] == ansArray[k+1] || ansArray[k] == ansArray[k+2]){
// genNewRandNum();
// ansArray[k] = ansValue;
// }else if(ansArray[k+1] == ansArray[k+2]){
// genNewRandNum();
// ansArray[k+1] = ansValue;
// }else{
// break;
// }
// }
if(ansValue!=rightAnsValue){
document.getElementById("box" + ansId).innerHTML = ansValue;
}else{
genNewRandNum();
document.getElementById("box" + ansId).innerHTML = ansValue;
}
}
}
}
The way I generate new numbers:
function genNewRandNum(){
rand1 = digitArray[Math.floor(Math.random() * digitArray.length)];
rand2 = digitArray[Math.floor(Math.random() * digitArray.length)];
ansValue = rand1 * rand2;
}
Replace your genNewRandNum() with below code. I have used IIFE to create a closure variable alreadyGeneratedNumbers thats available inside the function generateRandomNumber() thats returned.
So everytime genNewRandNum() is executed, it checks against alreadyGeneratedNumbers to make sure it always returns a unique between 1 and 9.
var genNewRandNum = (function(){
var alreadyGeneratedNumbers = {};
return function generateRandomNumber() {
var min = Math.ceil(1),
max = Math.floor(9);
randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
if(alreadyGeneratedNumbers[randomNumber]) {
return generateRandomNumber();
} else {
alreadyGeneratedNumbers[randomNumber] = randomNumber;
return randomNumber;
}
}
})();
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
console.log(genNewRandNum());
Note: If you call genNewRandNum() for the 10th time it will throw error. So if you have a use case where you would need to reset it after all numbers from 1 to 9 are returned, then you need to add code to handle that
The easiest way to brute-force this is to use accept/reject sampling. You can do something like so:
uniqueRandomNumbers = function(n, nextRandom)
{
var nums = {}; var m = 0;
while(m < n)
{
var r = nextRandom();
if(! nums.hasOwnProperty(r))
{
nums[r] = true; m++;
}
}
return Object.keys(nums);
}
Here I'm using the fact that js objects are implemented as hashmaps to get a hashset. (This has the downside of converting the numbers to strings, but if you're not planning on imediately doing arithmetic with them this is not a problem.)
In order to get four unique integers between 0 and 9 you can then do something like:
uniqueRandomNumbers(4, function() { return Math.floor(Math.random() * 10); })
If you want something a little better than brute force (which probably isn't relevant to your use case but could help someone googling this), one option is to go through each element and either take or leave it with an appropriate probability. This approach is outlined in the answers to this question.

Simplify IF...Else Statement with For Loop [Javascript]

Context: I am creating a table of content to inform user of the page(s) that they have completed/visited.
The if...else statement below is working (by itself) but i want to generate the check for the other 30 chapters using the "i" counter from the FOR loop.
The script will first load the localstorage that contains the value 1 or 0 representing visited / unvisited and transfer that data onto variable chap1check. based on the result, the table of content should then show the user which page have or have not been visited.
im not sure of what i need to do but in theory, i will need to replace all the "chap1..." with the value of "i".
<script type="text/javascript">
var i;
for (i = 1; i <=31; i++){
var chap1Check = localStorage.getItem("chap1Status");
if(chap1Check == "1"){
document.getElementById('chap1Completion').innerHTML = "Completed";
}else{
document.getElementById('chap1Completion').innerHTML = "Incomplete";
}
}
</script>
Just concatenate the part of the string before the number with the number (i), followed by the part of the string after the number.
for (var i = 1; i <= 31; i ++){
var chapCheck = localStorage.getItem("chap" + i + "Status");
document.getElementById('chap' + i + 'Completion').textContent = chapCheck == "1" ? "Completed" : "Incomplete";
}
The following code will work. However, it would be much cleaner for you to just store an Array in local storage, and access it by indexing the array. Also, take a look into using the map functor over a for loop.
Note also that you should inline the declaration of i in the for loop as shown below. Otherwise, you may get conflicts with any future use of i in a for loop.
for (var i = 1; i <=31; i++){
var chapterChecker = localStorage.getItem("chap"+i+"Status");
if(chap1Check == "1"){
document.getElementById('chap'+i+'Completion').innerHTML = "Completed";
}else{
document.getElementById('chap'+i+'Completion').innerHTML = "Incomplete";
}
}
A solution using es6 template string could be this
for (var i = 1; i <=31; i++){
let content = '';
if(localStorage.getItem(`chap${i}Status`) == "1"){
content = "Completed";
}else{
content = "Incomplete";
}
document.getElementById(`chap${i}Completion`).innerHTML = content;
}

.length on array crashing when length is 1 (maybe issue with split)

I'm having trouble with this code. I've tried to troubleshoot it many times and seem to have isolated the issue, but can't figure out the cause.
If the variable called string is set to something in the form of "text v. text," the code runs fine and the first if-statement triggers the sentence. If the string contains text but no "v." i.e. nothing that meets the search separator value, the function fails and does not execute the second if-statement.
Link to Fiddle: http://jsfiddle.net/qsq4we99/
Snippet of code, there also would need to be a html div with ID "outputtext."
function brokenCode()
{
//Setting Relevant Variables
var string = "red";
var array = string.split("v.");
var number = array.length;
// Checking location of things
var findText1 = array[0].search("search text");
var findText2 = array[1].search("search text");
//Running Conditional Stuff
if(number > 1)
{
document.getElementById('outputtext').innerHTML = "2+ listed";
}
else if(number < 2)
{
document.getElementById('outputtext').innerHTML = "1 listed";
}
}
brokenCode();
In this simplified example there is no clear explanation why the search operations need to occur (they are there because in the real code they are needed... but something about them seems to be causing the problem (even in this simple example). If the two searches are removed, the code runs smoothly.
You can't start setting variables from the array without checking for length. Before setting findText1 & findText2, check to make sure the length of the array is greater than zero.
function brokenCode() {
//Setting Relevant Variables
var string = "red";
var array = string.split("v.");
var number = array.length;
if (number > 0) {
// Checking location of things
var findText1 = array[0].search("search text");
var findText2 = array[1].search("search text");
//Running Conditional Stuff
if(number > 1)
{
document.getElementById('outputtext').innerHTML = "2+ listed";
}
else if(number < 2)
{
document.getElementById('outputtext').innerHTML = "1 listed";
}
}
}
brokenCode();

HTML form - value depends on two different drop-down menus

Hello javascript experts!
I'm a novice here, trying to create a script to add up membership fees on a website (I'm a volunteer). Any help is greatly appreciated.
I used this website: http://www.javascript-coder.com/javascript-form/javascript-calculator-script.phtml to set up my html.
It worked just fine (I set up 3 functions, one to calculate the membership price, another for the postage and the other the total amount due - I have not included them below but know they work fine).
UNTIL I realized that the value of postage (which I had calculated only using the first drop-down menu: id=country) was also dependant on the amount in the second drop down menu (the second drop-down menu's id: membership_type). That is to say, the postage is not only determined by country but also by membership type. I tried to set up a script that would vary the value of the postage depending on the value of the membership type but it isn't working.
I'm not a coder as you can tell so I've spent a lot of time looking for the correct way to do this but have come to a deadend....
var membership_prices = new Array();
membership_prices["regular"]=40;
membership_prices["student"]=24;
membership_prices["emeritus"]=24;
membership_prices["regularplus"]=62;
membership_prices["studentplus"]=46;
membership_prices["emeritusplus"]=46;
var extra_postage_cost = new Array();
extra_postage_cost["USA"]=0;
extra_postage_cost["Canada"]=0;
<!-- this is the part that needs work: Edited since original post -->
extra_postage_cost["Other_Country"]
if (document.getElementById('membership_type').value =="regular")
extra_postage_cost["Other_Country"]=8;
else if (document.getElementById('membership_type').value =="student")
extra_postage_cost["Other_Country"]=8;
else if (document.getElementById('membership_type').value =="emeritus")
extra_postage_cost["Other_Country"]=8;
else if (document.getElementById('membership_type').value =="regularplus")
extra_postage_cost["Other_Country"]=16;
else if (document.getElementById('membership_type').value =="studentplus")
extra_postage_cost["Other_Country"]=16;
else if (document.getElementById('membership_type').value =="emeritusplus")
extra_postage_cost["Other_Country"]=16;
else
extra_postage_cost["Other_Country"]=0;
<!-- end of what I believe needs work -->
Here is the rest of the code:
function getMembershipPrice ()
{
var membershipPrice=0;
var theForm = document.forms["membershipform"];
var selectedMembership = theForm.elements["membership_type"];
membershipPrice = membership_prices[selectedMembership.value];
return membershipPrice;
}
function getExtraPostagePrice ()
{
var extraPostagePrice = 0;
var theForm = document.forms["membershipform"];
var selectedPostage = theForm.elements["country"];
extraPostagePrice = extra_postage_cost[selectedPostage.value];
return extraPostagePrice;
}
function getAmountDue()
{
var amountDue = getMembershipPrice() + getExtraPostagePrice();
document.getElementById('amountDue').innerHTML ="Amount Due $"+amountDue;
}
In the drop-down menus themselves I have this kind of code inside the brackets for each drop-down menu:
select name="membership_type" id="membership_type" onchange="getAmountDue()"
select name="country" id="country" onchange="getAmountDue()"
I assume the country drop down is defaulted to USA. If this is the case, simply add an "onchange" event listener to the dropdowns that call a function to update the extra charge.
So instead of having an array object for extra postage, just have a variable extraPostage initially set to zero and update it when the drop downs change.
var extraPostage = 0;
function onSelectChange(){
var countrySelect = document.getElementById('countrySelect');
var typeSelect = document.getElementById('membership_type');
if(countrySelect.value != "Other Country"){
extraPostage = 0;
return;
}
else{
var type = typeSelect.value;
switch(type){
case "regular":
extraPostage = 8;
break;
case "student":
extraPostage = 8;
break;
case "emeritus":
extragPostage = 8;
break;
//keep going for each different possibility
}
}
You did good for being a self-proclaimed amateur. I think that you can probably more easily solve your problem with a multi-dimensional array so you can associate the membership prices with the corresponding postage:
var MEMBERSHIP = 0;
var POSTAGE = 1;
var membership_prices = new Array();
membership_prices["regular"] = [40, 8];
membership_prices["student"] = [24, 8];
membership_prices["emeritus"] = [24, 8];
membership_prices["regularplus"] = [62, 16];
membership_prices["studentplus"] = [46, 16];
membership_prices["emeritusplus"] = [46, 16];
Now, to access the membership price and postage price, if other country is selected, you can simply run:
membership_prices["regular"][MEMBERSHIP] // returns 40
membership_prices["regular"][POSTAGE] // returns 8
I used MEMBERSHIP and POSTAGE instead of 0 and 1 so it might make your code more readable when you're getting the values for the membership and the posting. Or if you prefer, you can simply access the inner array with the 0 and 1:
membership_prices["regular"][0] // returns 40
membership_prices["regular"][1] // returns 8
If I'm understanding what you're trying to do it looks like you were on the right track, but needed to invert how you're thinking about the logic in relation to the variable. You also had a few extra "s and {}s.
Try this in place of the code below <!-- this is the part that needs work: -->.
if (document.getElementById('membership_type').value =="regular")
extra_postage_cost["Other_Country"]= 8;
else if (document.getElementById('membership_type').value =="student")
extra_postage_cost["Other_Country"]= 8;
else if (document.getElementById('membership_type').value =="emeritus")
extra_postage_cost["Other_Country"]= 8;
else if (document.getElementById('membership_type').value =="regularplus")
extra_postage_cost["Other_Country"]= 16;
else if (document.getElementById('membership_type').value =="studentplus")
extra_postage_cost["Other_Country"]= 16;
else if (document.getElementById('membership_type').value =="emeritusplus")
extra_postage_cost["Other_Country"]= 16;
else
extra_postage_cost["Other_Country"]= 0;
Try this:
var otherCountryPostageCost = 0,
membershipType = document.getElementById('membership_type');
if (membershipType == "regular" || membershipType == "student" || membershipType == "emeritus"){
otherCountryPostageCost = 8;
} else if (membershipType == "regularplus" || membershipType == "emeritusplus") {
otherCountryPostageCost = 16;
}
extra_postage_cost["Other_Country"] = otherCountryPostageCost;
I'm a novice too :) I thought for some time the most readable way of adding other countries' extra postage, assuming there will be many, and concluded with the following:
extra_postage_cost["Country"] = [ (regular), (student), (emeritus) ]
Where each value in parentheses is a number. For example:
extra_postage_cost["Ostia"] = [10, 20, 30, 60, 60, 30];
For this to work we need to define some variables and functions. The idea is to link the numerical order of a type (student, regular) in membership_types to the numerical position in each of the extra postage cost arrays.
membership_types = ["regular", "student", "emeritus",
"regularplus", "studentplus", "emeritusplus"];
function membership_index(m_type) {
var index = -1;
for(var i = 0; i < membership_types.length; i++) {
if(m_type == membership_types[i]) {
index = i;
}
}
return index;
}
Then when retrieving, it's kinda ugly :( but we want to first address the country, then the numerical position for the cost, that matches the membership type.
extraPostage = extra_postage_cost[whichCountry][membership_index(whichMembership)];
The result is here : http://jsfiddle.net/TgdEW/
p.s. I think technically you're not creating arrays, but objects, so I declare variable_name = {}

realtime statistics script

I'm sure you know the case in which gmail shows the free storage on the "Lots of space" paragraph when you are on the login page. There is a counter running on that page and I'm curious to know how it works. Can me give me some pointers, links, tutorials, reads, explanations?
If you look at the source for that page you will find that it is a simple javascript function that just updates the number once a second. There is no magic involved and it is not live data in any way, just an estimate.
This is the code in charge of it. Looks like it is based on a time computation being mapped to a number of Bytes.
function updateQuota() {
if (!quota_elem) {
return;
}
var now = (new Date()).getTime();
var i;
for (i = 0; i < CP.length; i++) {
if (now < CP[i][0]) {
break;
}
}
if (i == 0) {
setTimeout(updateQuota, 1000);
} else if (i == CP.length) {
quota_elem.innerHTML = CP[i - 1][1];
} else {
var ts = CP[i - 1][0];
var bs = CP[i - 1][1];
quota_elem.innerHTML = format(((now-ts) / (CP[i][0]-ts) * (CP[i][1]-bs)) + bs);
setTimeout(updateQuota, 1000);
}
}
var PAD = '.000000';
You'll find a nice slideshow here
http://www.slideshare.net/kuchmuch/gmails-quota-secrets
Detailing how it works.
It is as mentioned just a date set to a predefined size, which the ticker then counts towards.

Categories

Resources