I have a google app script program that looks throughout a spreadsheet and moves rows with empty values in column 5 into another sheet (block 3 of code).
However, I keep getting a #error upon transfer because in certain rows, some phone numbers have a + in front of them. I tried to turn the entire number into a string (block 2 of code with foreach loop), but the error remains.
function insertRecord() {
var sheet_2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var sheet_1 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var rows = sheet_1.getDataRange().getValues();
rows.forEach(function(element) {
iterator=element.length;
for (i=0;i<iterator+1;i++){
if (typeof element[i]=== Number){
element[i]=element[i].toString
}
}
});
rows
.filter(row => row[5] == '')
.map(row_w_empty5col => sheet_2.appendRow(row_w_empty5col));
}
When I saw your script, at for (i=0;i<iterator+1;i++){, I thought that i<iterator+1 will be i<iterator. And, toString of element[i]=element[i].toString will be toString(). And also, when appendRow is used in a loop, the process cost will be high. Ref
And, about moves rows with empty values in column 5 into another sheet, in your script, .filter(row => row[5] == '') is used. In this case, the column 6 is checked. In order to check the column 5 (column "E"), it is row[4].
From your current issue and your goal, in your situation, how about copying the number format? When this is reflected in your script, it becomes as follows.
Modified script:
function insertRecord() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet_2 = ss.getSheets()[1];
var sheet_1 = ss.getSheets()[0];
var range = sheet_1.getDataRange();
var rows = range.getValues();
var f = range.getNumberFormats();
var n = rows.reduce((ar, r, i) => {
if (r[4].toString() == "") ar.push(i);
return ar;
}, []);
var values = n.map(e => rows[e]);
var formats = n.map(e => f[e]);
sheet_2.getRange(sheet_2.getLastRow() + 1, 1, values.length, values[0].length).setValues(values).setNumberFormats(formats);
}
References:
getNumberFormats()
setNumberFormats(numberFormats)
Update: Changed my code a little bit, and it works now.
function emailEditor() {
var app=SpreadsheetApp;
var targetSht=app.getActiveSpreadsheet().getSheetByName('Linkedin Only');
var spreadsheet=app.getActiveSpreadsheet().getSheets().length;
for (i=0;i<spreadsheet;i++){
var shts=app.getActiveSpreadsheet().getSheets()[i];
var originalData=shts.getDataRange().getValues();
var newData=originalData.filter(function(item){
return item[5]===''; //user declared input here
});
var begin=targetSht.getDataRange().getValues().length;
targetSht.getRange(begin+1,1,newData.length,newData[0].length).setValues(newData);
}
}
Related
I am very new to javascript and have searched around a ton for this and can't seem to find the issue with my code. I am attempting to simply write a code that will copy the values in a column from a pivot table sheet in Google Sheet and then paste the values in another sheet. However, before pasting the values, I want each individual value to be duplicated 12 times (for 12 months). So, assuming I have 10 unique values (A, B, C, D, E, F, G, H, I, J) that I am copying, I want to return value A 12 times in a row, then value B 12 times in a row, etc.
I run getValues, which seems to put the values in a 2 dimensional array. I've then taken this temp_array that I had created and used a for loop to duplicate each value 12 times in a new array.
However, when I setValues, I am pasting the values in my spreadsheet correctly, but I get this error message regardless (The number of columns in the data does not match the number of columns in the range. The data has 0 but the range has 1.), any ideas why?
Here is a small example of what my input could look like (1st image) and what I would want the output to look like (2nd image)
function test2() {
// activating current spreadsheet for use
var spreadsheet = SpreadsheetApp.getActive();
//empty array
var array_dept_temp = [];
// returns cell position (ex: C5) of the last row of the pivot table 1 sheet that has content in column 1
var last_row = spreadsheet.getSheetByName("Pivot Table 1").getRange("A:A").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRowIndex();
//subtracting 1 from last row because we are excluding the headers. This gives us our row_length
var row_length = last_row - 1
var array_dept = [[]]
array_dept = new Array(row_length*12)
//new Array(row_length*12);
// Get value in pivot table 1 from range of row 2 (dept name, but exclude the header), column 1, all the way to last row
// Then paste it in sheet5 from row 1, column 3, all the way to the last row defined above
array_dept_temp = spreadsheet.getSheetByName("Pivot Table 1").getRange(2,1, last_row).getValues();
for (var i = 1; i < row_length; i++ )
{
//get value and then paste it in a destination
array_dept.fill(array_dept_temp[i-1], (-12 + (12*i)) , 12*i);
}
var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2,3,row_length*12);
destination_dept.setValues(array_dept);
}
Suggestion / Alternate solution:
Try:
function test() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Pivot Table 1");
var array_dept_temp = sheet.getRange(2,1, sheet.getLastRow()-1).getValues();
var array_dept = [];
for (var i = 0; i < array_dept_temp.length; i++) {
array_dept = [...array_dept, ...Array.apply(null, Array(12)).map(function(){return array_dept_temp[i]})]
}
var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2,3,array_dept.length);
destination_dept.setValues(array_dept);
}
Result:
Another way without using fill or from.
Also some modification, you can just use .getLastRow() function to get the last row, however take not that if there is data below it will count all the rows including the blank until the row that has data. And you may also use .length on your data to setValue.
From your showing sample input and output situations, how about the following modified script?
Modified script:
function test2_sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcSheet = ss.getSheetByName("Pivot Table 1");
var dstSheet = ss.getSheetByName("Sheet5");
var srcValues = srcSheet.getRange("A2:A" + srcSheet.getLastRow()).getValues();
var dstValues = srcValues.flatMap(a => Array(12).fill(a));
dstSheet.getRange(2, 3, dstValues.length).setValues(dstValues);
}
When this script is run using your sample input sheet, I think that your expected output values are obtained.
Now, I thought that var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e])); can be modified to var dstValues = srcValues.flatMap(a => Array(12).fill(a));. This is simpler.
From your reply of Are you able to explain what this does? var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e]));, in this script, var dstValues = srcValues.flatMap(([a]) => Array(12).fill(a).map(e => [e])); can be also modified as follows. I thought that this might also help to understand it.
function test2_sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcSheet = ss.getSheetByName("Pivot Table 1");
var dstSheet = ss.getSheetByName("Sheet5");
var srcValues = srcSheet.getRange("A2:A" + srcSheet.getLastRow()).getValues();
var dstValues = [];
for (var i = 0; i < srcValues.length; i++) {
dstValues = dstValues.concat(Array(12).fill(srcValues[i]));
}
dstSheet.getRange(2, 3, dstValues.length).setValues(dstValues);
}
Note:
As additional information, when your showing script is modified, how about the following modification? In your script, I thought that it is required to add the values to array_dept in the loop. And, it is required to flatten the elements in the array.
function test2() {
var spreadsheet = SpreadsheetApp.getActive();
var array_dept_temp = [];
var last_row = spreadsheet.getSheetByName("Pivot Table 1").getRange("A:A").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRowIndex();
var row_length = last_row - 1
var array_dept = []
array_dept_temp = spreadsheet.getSheetByName("Pivot Table 1").getRange(2, 1, last_row).getValues();
for (var i = 0; i < row_length; i++) {
array_dept = [...array_dept, ...Array(12).fill(array_dept_temp[i])];
}
var destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(2, 3, array_dept.length);
destination_dept.setValues(array_dept);
}
Reference:
flatMap()
I have the below spreadsheet that I would like to AutoFill the persons name. the issue is that there are blank rows between the names. Each name is in line with a sku2 and needs to be inline with all locations. there can be up to 10 blank rows (due to how many locations).
if I could loop this maybe
function LoopTillLr() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A2').activate();
spreadsheet.getActiveRange().autoFillToNeighbor(SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
};
Appreciate any help
If you only want to replicate the NAME values against variable LOCATION values then use this script:
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
var lastRow = ss.getDataRange().getLastRow();
for (var i = 1; i < lastRow+1; i++) {
if (ss.getRange(i,1).getValue() == "") {
var value = ss.getRange(i-1,1).getValue();
ss.getRange(i,1).setValue(value);
}
}
}
Ensure that A2 is not empty else the script will fail.
If it is a lot of records, you can create a function and run it. The following does this until the end of the sheet, so make sure to delete all the rows towards the end which you do not need or adjust the range in the 2nd row.
function autoFillDown(){
const range = SpreadsheetApp.getActiveSheet().getRange("A:A");
const rows = range.getValues();
let outputArray = [];
rows.forEach( row => {
// if it contains a name, leave it
if( row[0].length > 1){
outputArray.push( [row[0]] )
// otherwise replace it with the value above it
} else {
outputArray.push( [outputArray[outputArray.length-1]] );
}
});
range.setValues( outputArray );
}
I am trying to delete unneeded sheets from a template once relevant information has been copied across. To do this I am looking up the sheet name with a check list. If the lookup returns a value of 0.0 then I want to delete the sheet.
function myFunction() {
var studentsheet = SpreadsheetApp.openById('1Qj9T002nF6SbJRq-iINL2NisU7Ld0kSrQUkPEa6l31Q').;
var sheetsCount = studentsheet.getNumSheets();
var sheets = studentsheet.getSheets();
for (var i = 0; i < sheetsCount; i++){
var sheet = sheets[i];
var sheetName = sheet.getName();
Logger.log(sheetName);
var index = match(sheetName);
Logger.log(index);
if (index = "0.0"){
var ss = studentsheet.getSheetByName(sheetName).activate();
ss.deleteactivesheet();
}
else {}
}
function match(subject) {
var sourcesheet = SpreadsheetApp.openById('14o3ZG9gQt9RL0iti5xJllifzNiLuNxWDwTRyo-x9STI').getSheetByName("Sheet6").activate();
var lookupvalue = subject;
var lookuprange = sourcesheet.getRange(2, 2, 14, 1).getValues().map(function(d){ return d[0] });
var index = lookuprange.indexOf(subject)+1;
return index;
}
};
The problem is at the end when trying to delete the sheet. I have amended the code so it selects the sheet and makes it active but in the next line I am not allowed to call .deleteactivesheet(). Does anyone know how I can write this end part where I can select the sheet based on the index score being 0 and then delete it?
To delete a Sheet from a Spreadsheet, there are two applicable Spreadsheet class methods (as always, spelling and capitalization matter in JavaScript):
Spreadsheet#deleteSheet, which requires a Sheet object as its argument
Spreadsheet#deleteActiveSheet, which takes no arguments
The former is suitable for any type of script, and any type of trigger, while the latter only makes sense from a bound script working from a UI-based invocation (either an edit/change trigger, menu click, or other manual execution), because "activating" a sheet is a nonsense operation for a Spreadsheet resource that is not open in a UI with an attached Apps Script instance.
The minimum necessary modification is thus:
var index = match(sheet);
if (index === 0) { // if Array#indexOf returned -1 (not found), `match` returned -1 + 1 --> 0
studentsheet.deleteSheet(sheet);
}
A more pertinent modification would be something like:
function deleteNotFoundSheets() {
const studentWb = SpreadsheetApp.openById("some id");
const lookupSource = getLookupRange_(); // assumes the range to search doesn't depend on the sheets that may be deleted.
studentWb.getSheets().filter(function (s) {
return canDelete_(lookupSource, s.getName());
}).forEach(function (sheetToDelete) {
studentWb.deleteSheet(sheetToDelete);
});
}
function getLookupRange_() {
const source = SpreadsheetApp.openById("some other id");
const sheet = source.getSheetByName("some existing sheet name");
const r = sheet.getRange(...);
return r.getValues().map(...);
}
function canDelete_(lookupRange, subject) {
/** your code that returns true if the subject (the sheet name) should be deleted */
}
This modification uses available Array class methods to simplify the logic of your code (by removing iterators whose only purpose is to iterate, and instead expose the contained values to the anonymous callback functions). Basically, this code is very easily understood as "of all the sheets, we want these ones (the filter), and we want to do the same thing to them (the forEach)"
Additional Reading:
JavaScript comparison operators and this (among others) SO question
Array#filter
Array#forEach
If just like me you have been struggling to find a working example of #tehhowch above solution, here it is:
function deleteSheetsWithCriteria() {
let ss = SpreadsheetApp.getActiveSpreadsheet(),
sheetList = ss.getSheetByName('List'),
list = sheetList.getRange('A1:A'+sheetList.getLastRow()),
lookupRange = list.getValues().map(function(d){ return d[0] }) + ',List'; // List of sheets NOT to delete + lookuprange sheet
Logger.log(lookupRange)
//Delete all sheets except lookupRange
ss.getSheets().filter(function (sheet) {
return deleteCriteria_(lookupRange, sheet.getName());
}).forEach(function (sheetToDelete) {
ss.deleteSheet(sheetToDelete);
});
}
function deleteCriteria_(lookupRange, sheet) {
var index = lookupRange.indexOf(sheet);
Logger.log(index)
if (index > 0) {0} else {return index}; // change line 19 to 'return index;' only, if you want to delete the sheets in the lookupRange, rember to remove the lookupRange in variable LookupRage
}
I have a Google Sheets file with 3 spreadsheets. Each of the first 2 contains numerous number data entries. All I want to do is change one cell value on the third (tt) if any data is changed on a different sheet. Looking at my incomplete onEdit() script, you will see I obviously don’t know what I’m doing. How to I change the value of cell ‘a5’ on third spreadsheet (tt) for any changes made anywhere on only the first two?
function onEdit(event)
{
var aa = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input-Expenses");
var ss = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input-Income-n-Assets");
var tt = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Run Program");
var r = event.aa.getActiveRange('a1:r105');
var s = event.ss.getActiveRange('d1:q94');
var g = 0
tt.getRange('a5').setValue(g);
}
You need to refer to this article:
https://developers.google.com/apps-script/guides/triggers/
Find Edit trigger.
range A Range object, representing the cell or range of cells that
were edited.
Usage:
var editedRange = event.range;
var editedSheet = editedRange.getSheet();
if (editedSheet.getName() === 'Sheet1')
{
var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
// do smth
}
This should do what you want
function onEdit(event) {
try {
var aName = event.range.getSheet().getName(); // Active sheet
if( ( aName === "Input-Expenses" ) || ( aName === "Input-Income-n-Assets" ) ) {
// You said anywhere on these 2 sheets and put in Run Program:A5
event.source.getSheetByName("Run Program").getRange("A5").setValue(0);
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}
I’m writing a custom function in Google Apps Script that, if a certain other cell contains a number, uses the value of that cell and several other cells to calculate a result. Otherwise, if that certain other cell does not contain a number, the function should just return an empty string so that the active cell appears blank.
I was able to come up with a working function to do this, but in order to protect sensitive information, I’m not going to copy it here. Instead, here’s an example function that accomplishes the same thing:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rowNum = sheet.getActiveCell().getRow();
var rowVals = sheet.getRange(rowNum, 1, 1, 15).getValues();
var fVal = rowVals[0][5];
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
var aVal = rowVals[0][0];
var bVal = rowVals[0][1];
var cVal = rowVals[0][2];
var gVal = rowVals[0][6];
return ((gVal * fVal) + aVal + bVal + cVal);
}
However, in an effort to speed it up (and also some other reasons that would be complicated to try to explain here, so you'll have to just trust me), I want to have the custom function set the value of the cell to be a formula instead of doing the calculating itself. It doesn’t work to just put the formula in the cell in the first place because then it still calculates/shows a result even if column F doesn’t contain a number.
Here’s what I’ve tried so far:
function myFunction2() {
var sheet = SpreadsheetApp.getActiveSheet();
var rowNum = sheet.getActiveCell().getRow();
var fVal = sheet.getRange(rowNum, 6).getValue();
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
var formula = '=SUM((G2*F2)+A2+B2+C2)';
return formula;
}
^This just makes the cell display the string “=SUM((G2*F2)+A2+B2+C2)”.
So I then tried using setFormula on the active cell:
function myFunction3() {
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.getActiveCell();
var rowNum = cell.getRow();
var fVal = sheet.getRange(rowNum, 6).getValue();
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
cell.setFormula('=SUM((G2*F2)+A2+B2+C2)');
}
^which, when I called the function in a cell, returned an error saying “You do not have permission to call setFormula”. The same thing happened when I tried getting the a1 notation of the active cell and then using getRange(a1Notation).setFormula('=SUM((G2*F2)+A2+B2+C2)') instead of calling setFormula directly on the active cell.
Anybody know if there's a way around that permission error? or have any other ideas for how to accomplish this?
The permission error is because of restrictions on what user defined functions can do. You can, however, do this with onEdit like this:
function onEdit(e) {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet();
var col = e.range.getColumn();
var rowNum = e.range.getRow();
if(col==6){
var fVal = s.getRange(rowNum,col,1, 1).getValue()
}
if (fVal == "" || isNaN(fVal)) {
return
}
else{
s.getRange(rowNum,col,1, 1).setFormula('=SUM((G2*F2)+A2+B2+C2)');
}}
I actually ended up figuring out a way to accomplish what I wanted using the built-in IF function, so that's what I did.