I coded something for google apps script. It is to increment ID +1 based on the last row. Everything is working so far except for the numbering of the new ID, instead of appearing as a number.
The result appears as R-NaN instead of R-002 or something similar
What do you think should I revise in my code? Thank you.
function new_item() {
// Get current spreadsheet
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var mysheet = ss.getActiveSheet();
// Set date to today in update field at the top of the sheet
var now = new Date();
mysheet.getRange(1,4).setValue(now);
// Last non-empty row
var rlast = mysheet.getLastRow();
Logger.log("Last row = " + rlast);
// Insert Row below
mysheet.insertRows(rlast+1);
var r = rlast+1;
// Copy format from row above
var sourcerange = mysheet.getRange(rlast + ":" + rlast);
var targetrange = mysheet.getRange(r + ":" + r);
sourcerange.copyTo(targetrange, {formatOnly:true});
// Col. 2 : Risk identity
var riskid = mysheet.getRange(rlast,2).getValue();
if (riskid.length > 3){
// Extract number ex. 3
var riskidnb = riskid.substring(1,riskid.length);
// Increase risk number +1
riskidnb++
// Convert to string "0004"
var s = "000" + riskidnb
// Write risk nb i.e. "R004"
mysheet.getRange(r,2).setValue("R-"+ s.substring(s.length-4))
}
``ยด
Explanation / Issue:
Your code really depends on the value of the cell in column B last row:
var riskid = mysheet.getRange(rlast,2).getValue();
There are two scenarios but I believe the second applies to your issue:
If the value in the cell is a number (e.g. 35233) then riskid will be an integer and therefore riskid.length will return null and as a result the if condition will evaluate to false. In this case, you can either use getDisplayValue or toString() instead to get the number as string and then you can apply .length to it:
var riskid = mysheet.getRange(rlast,2).getValue();
If the value in the cell is a string (e.g. R112) then the if condition will evaluate to true. If you do that:
var riskidnb = riskid.substring(1,riskid.length);
riskidnb will be 112 but this is still a string and therefore if you do riskidnb++ you will get NAN like the issue you have right now. In order to fix that, convert riskidnb to integer:
var riskidnb = parseInt(riskid.substring(1,riskid.length));
then you can do riskidnb++ and finally convert it back to string:
var s = "000" + riskidnb.toString();
Solution:
function new_item() {
// Get current spreadsheet
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var mysheet = ss.getActiveSheet();
// Set date to today in update field at the top of the sheet
var now = new Date();
mysheet.getRange(1,4).setValue(now);
// Last non-empty row
var rlast = mysheet.getLastRow();
Logger.log("Last row = " + rlast);
// Insert Row below
mysheet.insertRows(rlast+1);
var r = rlast+1;
// Copy format from row above
var sourcerange = mysheet.getRange(rlast + ":" + rlast);
var targetrange = mysheet.getRange(r + ":" + r);
sourcerange.copyTo(targetrange, {formatOnly:true});
// Col. 2 : Risk identity
var riskid = mysheet.getRange(rlast,2).getValue();
if (riskid.length > 3){
// Extract number ex. 3
var riskidnb = parseInt(riskid.substring(1,riskid.length));
// Increase risk number +1
riskidnb++
// Convert to string "0004"
var s = "000" + riskidnb.toString();
// Write risk nb i.e. "R004"
mysheet.getRange(r,2).setValue("R-"+ s.substring(s.length-4))
}
}
Output:
Related
I am trying to fetch the last row's row id (located in the first column) to increment it by one once there is a new row added. However I am not sure on how to convert the string into a number to increment by 1
What I tried:
var url = "";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
var rowID = ws.getRange(ws.getLastRow(), 1).getValues().toString().parseInt()+1;
console.log(rowID);
Which is not a function based on the log.
If the values of your id is 1, 2, 3,,, , how about the following modification?
From:
var rowID = ws.getRange(ws.getLastRow(), 1).getValues().toString().parseInt()+1;
To:
var rowID = (parseInt(ws.getRange(ws.getLastRow(), 1).getValue(), 10) || 0) + 1;
or
var rowID = (Number(ws.getRange(ws.getLastRow(), 1).getValue()) || 0) + 1;
or, in the event the value in the last row is known to always be a number or a blank, the following modification might be able to be used.
var rowID = ws.getRange(ws.getLastRow(), 1).getValue() + 1;
Reference:
parseInt()
Hi so i created a script in sheets to pull a value from a cell everyday but im looking to add another row which shows profit or loss for the day.
// Record history from a cell and append to next available row
function recordValue() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Matt");
var formattedDate = Utilities.formatDate(new Date(), "GMT", "dd/MM/yy");
Logger.log(formattedDate);
var value =sheet.getRange("$c$14").getValue();
// var profit = ((cell to the left) - (cell above the cell to the left));
sheet.appendRow([,formattedDate, value, profit]);
}
I'm looking to create the variable "profit" by grabbing the value from the cell to the left of the active cell and subtracting the cell above that one (left and up one from the active cell). Any help would be appreciated, thanks
this image shows what i want to be part of the script
Doing some operations with the column letter and the row number:
it is almost your code, except that we have two new variables
cellLeftValue and cellAboveLeftValue, both of them calculate the left by using String.fromCharCode to calculate the left letter:
function recordValue() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Matt");
var formattedDate = Utilities.formatDate(new Date(), "GMT", "dd/MM/yy");
Logger.log(formattedDate);
var rowIndex = 14;
var colIdx = 'c' ;
var leftColIdx = String.fromCharCode(colIdx.charCodeAt(0) - 1);
var value =sheet.getRange("$" + colIdx + "$" +rowIndex).getValue();
var cellLeftValue = sheet.getRange("$" + leftColIdx + "$" +rowIndex).getValue();
var cellAboveLeftValue = sheet.getRange("$" + leftColIdx + "$" + (rowIndex -1)).getValue();
var profit = cellLeftValue - cellAboveLeftValue ;
sheet.appendRow([,formattedDate, value, profit]);
}
you should add code to validate corner cases.
I'm a bit of newbie at coding, especially Javascript/Google-script language. I've created the code below, and it works, but now that I've got a working code I'd like to see how I can optimize it. It seems to me that all of the getValue() calls are a major performance hit, and I've never really been good at optimizing loops. Anyone know a better way to accomplish the same as this code?
What it does: Checks each spreadsheet in one of my folders to see if it needs to have the rest of the script run. If true, it opens that sheet and counts the number of rows that have data, using that to limit the amount of rows it checks in the loop. It then looks for any row marked for push and copies that range to another spreadsheet in my drive. It then continues to the next file in the folder and does the same.
Here's my code:
function myVupdate() {
try {
var folder = DriveApp.getFolderById("123abc"),
files = folder.getFiles();
while (files.hasNext()) {
var file = files.next(),
sss = SpreadsheetApp.open(file);
SpreadsheetApp.setActiveSpreadsheet(sss);
//Work orders update
var ss = sss.getSheetByName("Sheet2"),
refresh = ss.getRange("W3").getValue();
if (refresh == 0) {continue};
var avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 23).getValue();
if (cell == 0) {
var cells = [["v" + "WO-" + val[row-1][3] + "_" + val[row-1][2],val[row-1][13],val[row-1][14],val[row-1][15],new Date()]];
var tss = SpreadsheetApp.openById("target_spreadsheet"),
ts = tss.getSheetByName("Sheet5");
ts.insertRowBefore(2);
var last_hmy = ts.getRange(3,1).getValue();
ts.getRange(2,1).setValue(last_hmy+1);
ts.getRange(2,2,cells.length,cells[0].length).setValues(cells);
ts.getRange(2,7).setValue(sss.getName());
ss.getRange(row,17).setValue(last_hmy+1);
ss.getRange(row,18,cells.length,cells[0].length).setValues(cells);
//Turnover update
var ss = sss.getSheetByName("Sheet1"),
avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
}
}
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 24).getValue();
if (cell == 0) {
var cells = [["v" + val[row-1][3] + "_" + val[row-1][2],val[row-1][12],val[row-1][15],val[row-1][16],new Date()]];
var tss = SpreadsheetApp.openById("target_spreadsheet"),
ts = tss.getSheetByName("Sheet5");
ts.insertRowBefore(2);
var last_hmy = ts.getRange(3,1).getValue();
ts.getRange(2,1).setValue(last_hmy+1);
ts.getRange(2,2,cells.length,cells[0].length).setValues(cells);
ts.getRange(2,7).setValue(sss.getName());
ss.getRange(row,18).setValue(last_hmy+1);
ss.getRange(row,19,cells.length,cells[0].length).setValues(cells);
}
}
}
}
catch(e) {
// Browser.msgBox("An error occured. A log has been sent for review.");
var errorSheet = SpreadsheetApp.openById ("target_sheet").getSheetByName("Error Log"),
source = sss.getName();
lastRow = errorSheet.getLastRow();
var cell = errorSheet.getRange('A1');
cell.offset(lastRow, 0).setValue(e.message);
cell.offset(lastRow, 1).setValue(e.fileName);
cell.offset(lastRow, 2).setValue(e.lineNumber);
cell.offset(lastRow, 3).setValue(source);
cell.offset(lastRow, 4).setValue(new Date());
MailApp.sendEmail("my#email.com", "Error report - " + new Date(),
"\r\nSource: " + source + "\r\n"
+ "\r\nMessage: " + e.message
+ "\r\nFile: " + e.fileName
+ "\r\nLine: " + e.lineNumber
);
}
}
Hello and welcome to Stack Overflow,
first of all, you are correct. The more getValue(), or setValue() calls you do the worse the performance, read more on best practices here. Google recommends you batch these as much as possible. One thing that immediately springs to attention is the following:
var val = ss.getDataRange().getValues();
so now you have all the values on the sheet in a 2D array. That means that in the following bit
var ss = sss.getSheetByName("Sheet2"),
refresh = ss.getRange("W3").getValue();
if (refresh == 0) {continue};
var avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 23).getValue();
every single getValue() or getValues() is no longer necessary. Instead, you know that refresh = val[2][22] because you need the 3rd row and 23rd column, as you already have the entire range that has data from that sheet.
Same with avals as all values in range D5:D are in vals[n][3], where n starts from 4. Remember, the array index starts from 0 (so first row and first column is vals[0][0].
So anywhere you are trying to use getValues() from the ss spreadsheet, you already have that data. What you can also do, is manipulate the array you have, so you always change the values only in that array. Once you are done with it, you use ss.getDataRange().setValues(vals) to push the entire array back to the same range (you can just store the range in a variable like datRange = ss.getDataRange() and then do datRange.setValues(vals).
You will just need to work with a separate data array for any other sheet. I did not go into detail for the rest of the code as the same ideas go throughout. Since you already grab everything with getValues() there is no longer any reason to use getValue() for any cell within that range.
I modified the code this way... it gathers data from all the sheets and finds only the rows that have data, BUT now I am having a problem modifying the range with each pass so that it is equal to the number of rows that do have value (found with (values[row][0] != '')). I have put a ??? in the spot where I am trying to have a variable height.
function getAllData() {
var folder = DocsList.getFolderById("folderid");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Base")
sheet.clearContents();
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet 1");
var lastLine = sheet2.getLastRow();
var values = sheet2.getRange('A3:J').getValues();
var formulas = sheet2.getRange('A3:J').getFormulas();
var data = [];
for(var row = 0 ; row < (values).length ; row++){
var lastrow = sheet.getLastRow()+1;
if (values[row][0] != '') {
for(var col = 0 ; col < formulas[row].length ; col++){
if(formulas[row][col] != '')
{values[row][col] = formulas[row][col]};
data.push(values[row]);}
if(data.length > 0)
sheet.getRange(lastrow, 1, ???, data[0].length).setValues(data);
}
}
};
}}
You are using getValue() as opposed to getValues() (With a letter "s" on the end)
var onecell = posheet.getRange('B4').getValue();
The documentation states:
getValue() - Returns the value of the top-left cell in the range.
The parameter for getRange() is kind of tricky and not well documented.
For example this:
getRange(2, 3, 6, 4)
gets a range from C2 to G8. Figure that out. The first number is the number 2, which is for the row 2. The second number is 3, for the third column (which is C). The third and fourth numbers are relative to the first two numbers.
Also, you are using: appendRow([array]) which uses an array for the parameter. So you must make sure that the data is in the form of an array, or use something else.
Here is the link for getValues:
Google Documentation - getValues
The example is this code:
// The code below will get the values for the range C2:G8
// in the active spreadsheet. Note that this will be a javascript array.
var values = SpreadsheetApp.getActiveSheet().getRange(2, 3, 6, 4).getValues();
Logger.log(values[0][0]);
Here is code that seems to work:
function getAllData() {
var folder = DocsList.getFolderById("Your file ID");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
sheet.clearContents();
sheet.appendRow(["Value from Sheet One", "Range of values from Sheet Two"]);
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
//Reset to null on every iteration
var onecell = null;
var theRange = null;
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet1 = SpreadsheetApp.open(file).getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet2");
// The code below will get the values for the range A3:A9
// in the active spreadsheet. Note that this will be a javascript array.
onecell = sheet1.getRange('B4').getValue();
theRange = sheet2.getRange(1,3,1,6).getValues();
Logger.log('onecell: ' + onecell);
Logger.log('onecell[0][0]: ' + onecell[0][0]);
Logger.log('theRange: ' + theRange)
Logger.log('theRange[0][0]: ' + theRange[0][0])
var multipleValues = [theRange[0][0], theRange[0][1], theRange[0][2], theRange[0][3], theRange[0][4]];
Logger.log('multipleValues: ' + multipleValues);
sheet.appendRow([onecell, "'" + multipleValues]);
};
}
}
In the first column, it only enters one value into the sheet cell. In the second column, the cell gets multiple values put into it from the row. In other words, and entire rows values, and combined and put into one cell. I think that's what you want from the code.
If you try to put an array into a spreadsheet cell, instead of showing the array of values as text, it shows something like an object. So I put a quote in front of the values so the cell formatting would default to text.
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));
}