I'm trying to do two things. Get this to work, and get it to work as efficient as possible.
The task:
To take a user defined RANGE like the following:
What I want to do is simply run a foreach loop that will go through the selection and pull out specific cells then place them into a copy of a template document
var ranger = sheet.getSelection().getActiveRangeList().getRanges().forEach(function(dataArray)
// dataArray is my object[][] Casting all data to strings just in case.
var songname = String(dataArray[1]);
var songwriters = String(dataArray[2]);
var publishers = String(dataArray[3]);
var artist = String(dataArray[5]);
var useremail = String(dataArray[6]);
...
}
Later in my code, I open the template, replace the placeholders, rename and save it.
But I just want to get there, and get there as efficiently as possible.
Is this going to do it?
I'm very confused by the callback formatting in Google App Scripting.
function myFunction() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var dataArray=[];
sh.getSelection().getActiveRangeList().getRanges().forEach(function(r,i){
dataArray.push(r.getValues());
});
....
dataArray is now and array of 2 dimensional arrays so it's a 3 dimensional array.
Related
I am trying to get print the names of all the sheets in a Google Spreadsheet in one column and their respective IDs (part of the URL) in another column. Here is my script so far
function getSchedule() {
var ss=SpreadsheetApp.openById("Sheet_ID");
var sh=ss.getActiveSheet();
var allSheets=ss.getSheets();
var ids=[];
for(var i=0;i<allSheets.length;i++)
{
ids[allSheets[i].getName()]=allSheets[i].getSheetId();
}
var invoicess = SpreadsheetApp.getActiveSpreadsheet();
var schedule = invoicess.getSheetByName("Schedule");
schedule.getRange("A1:B").clear();
var headers = [["Sheet Name", "ID"]];
schedule.getRange("A1:B1").setValues(headers);
for(var d = 1; d=allSheets.length;d++){
schedule.getRange(d+1,1).setValue(allSheets[d]);}
}
So I believe that allSheets should be an array of all the sheet names and ids is an array of the IDs, but the last for loop that I believe should print the values of the allSheets array does not print anything. I would like to have it print starting in cell A2. I do not have a loop for the IDs yet. Can anyone tell me why nothing is printing in the cells?
Issues:
The following line is not going to add new values to the array:
ids[allSheets[i].getName()]=allSheets[i].getSheetId();
As a result, data are not added to the array and this is the reason you are not seeing data in your sheet. Instead you should use push() to add elements (rows) to the array.
Also since you are pasting a 2D array of values, you need to use setValues instead of setValue.
Your code uses unnecessary lines of code. For example you use setValues to add the header in your file when you can simply add it to the original array in which the data is going to be appended. You don't need for loops to append values with setValues.
I improved your code by removing redundant calls and made it more JavaScript friendly.
Improved Solution:
function getSchedule() {
const ss=SpreadsheetApp.openById("Sheet_ID");
const invoicess = SpreadsheetApp.getActiveSpreadsheet();
const schedule = invoicess.getSheetByName("Schedule");
const allSheets = ss.getSheets();
const ids = [["Sheet Name", "ID"]];
allSheets.forEach(sh=>{
ids.push([sh.getName(),sh.getSheetId()]);
});
schedule.getRange("A1:B").clear();
schedule.getRange(1,1,ids.length,ids[0].length).setValues(ids);
}
Instead of this ids[allSheets[i].getName()]=allSheets[i].getSheetId();
try this: ids.push([allSheets[i].getName(),allSheets[i].getSheetId()])
and use setValues(ids) at the end.
The aim of my script is to loop through one column of data (Col 2 in my example) and where the cell says 'Approved' then adjust the formula which is sitting in the corresponding Col1 to be saved as a value. The script below achieves this but runs awfully slowly - can anyone help in speeding it up?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName('data');
var tracker = ss.getSheetByName('Tracker');
var rowlength = tracker.getLastRow();
for (r=2; r<rowlength+1; r++) {
var ApprovedCell = tracker.getRange(r,2).getValue();
if (ApprovedCell == 'Approved'){
var FormulaCell = tracker.getRange(r,1);
FormulaCell.copyTo(FormulaCell,{contentsOnly:true});
}}
}
Explanation:
The issue with your current solution is that you are iteratively using getValue, getRange and copyTo which is an extremely inefficient approach especially when the size of the data becomes large.
Instead you can use getValues() and getFormulas() to get all the values and the formulas respectively, of the given range, and then use setValues() to set all the desired values/formulas back to the sheet.
The following script will iterate over the values with a forEach() loop and will store the value, if cell in column B is 'Approved', otherwise store the formula, to an empty array repAr.
Then you can efficiently use a single line of code to set all the values/formulas back to column A:
tracker.getRange(2,1,repAr2D.length,1).setValues(repAr2D);
Solution:
function myFunction(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tracker = ss.getSheetByName('Tracker');
const vals = tracker.getRange('A2:B'+tracker.getLastRow()).getValues();
const formulas = tracker.getRange('A2:A'+tracker.getLastRow()).getFormulas().flat();
const repAr = [];
vals.forEach((r,i)=>
repAr.push(r[1]=='Approved'?r[0]:formulas[i]));
const repAr2D = repAr.map(r=>[r]);
tracker.getRange('A2:A'+tracker.getLastRow()).clearContent();
tracker.getRange(2,1,repAr2D.length,1).setValues(repAr2D);
}
Bonus info:
Instead of a regular if condition, I used a ternary operator to make the code more clear.
getFormulas() returns a 2D array and this is why I am using flat() to convert it to a 1D array. This is more convenient given that the range is a single column and you can just slice the array with a single index.
It is a good practice to clear the content before you set data to a range that already contains some data.
I'm new to Google Apps Script and I'm trying to make a script where I'll take a single string value and copy to multiple rows in a google sheet. I've taken an array to save the single value multiple times. But still I can't get it done. Every time I run the script, I get this error,
Cannot convert Array to Object[][]
Here are my codes,
function myFun() {
var ss = SpreadsheetApp.openById(SHEET_ID);
var sheet = ss.getSheetByName("Form Responses");
var new_vals = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var master_ss = SpreadsheetApp.getActiveSpreadsheet();
var master_sheet = master_ss.getSheetByName("Sheet1");
var lr = master_sheet.getLastRow()+1;
var ss_real_name = "District";
var ss_real_names = [];
for (var i=0; i<new_vals.length; i++)
{
ss_real_names.push(ss_real_name);
}
master_sheet.getRange(lr, 1, new_vals.length).setValues(ss_real_names);
}
Is there something wrong in my code? How can I save the single string value in multiple rows?
Google Apps script writes values as arrays of arrays with every array inside of the outer array being a row and the elements in the inner arrays going into the columns.
If you want to write the data as rows you need to create an array filled with one element arrays. Try ss_real_names.push([ss_real_name]);.
If you wanted to write them as a column vector you could just say setValues([ss_real_names]) instead.
I am writing a script for a Google Docs Spreadsheet to read a list of directors and add them to an array if they do not already appear within it.
However, I cannot seem to get indexOf to return anything other than -1 for elements that are contained within the array.
Can anyone tell me what I am doing wrong? Or point me to an easier way of doing this?
This is my script:
function readRows() {
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
var numRows = column.getNumRows();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var directors = new Array();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (directors.indexOf(row) == -1) {
directors.push(row);
} else {
directors.splice(directors.indexOf(row), 1, row);
}
}
for (var i = 2; i < directors.length; i++) {
var cell = sheet.getRange("F" + [i]);
cell.setValue(directors[i]);
}
};
When you retrieve values in Google Apps Script with getValues(), you will always be dealing with a 2D Javascript array (indexed by row then column), even if the range in question is one column wide. So in your particular case, and extending +RobG's example, your values array will actually look something like this:
[['fred'], ['sam'], ['sam'], ['fred']]
So you would need to change
var row = values[i];
to
var row = values[i][0];
As an aside, it might be worth noting that you can use a spreadsheet function native to Sheets to achieve this (typed directly into a spreadsheet cell):
=UNIQUE(Director)
This will update dynamically as the contents of the range named Director changes. That being said, there may well be a good reason that you wanted to use Google Apps Script for this.
It sounds like an issue with GAS and not the JS. I have always had trouble with getValues(). Even though the documentation says that it is a two dimensional array, you can't compare with it like you would expect to. Although if you use an indexing statement like values[0][1] you will get a basic data type. The solution (I hope there is a better way) is to force that object into a String() and then split() it back into an array that you can use.
Here is the code that I would use:
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
values = String(values).split(",");
var myIndex = values.indexOf(myDirector);
If myDirector is in values you will get a number != -1. However, commas in your data will cause problems. And this will only work with 1D arrays.
In your case: var row = values[i]; row is an object and not the string that you want to compare. Convert all of your values to an array like I have above and your comparison operators should work. (try printing row to the console to see what it says: Logger.log(row))
I ran into a similar problem with a spreadsheet function that took a range as an object. In my case, I was wanting to do a simple search for a fixed set of values (in another array).
The problem is, your "column" variable doesn't contain a column -- it contains a 2D array. Therefore, each value is it's own row (itself an array).
I know I could accomplish the following example using the existing function in the spreadsheet, but this is a decent demo of dealing with the 2D array to search for a value:
function flatten(range) {
var results = [];
var row, column;
for(row = 0; row < range.length; row++) {
for(column = 0; column < range[row].length; column++) {
results.push(range[row][column]);
}
}
return results;
}
function getIndex(range, value) {
return flatten(range).indexOf(value);
}
So, since I wanted to simply search the entire range for the existance of a value, I just flattened it into a single array. If you really are dealing with 2D ranges, then this type of flattening and grabbing the index may not be very useful. In my case, I was looking through a column to find the intersection of two sets.
Because we are working with a 2D array, 2dArray.indexOf("Search Term") must have a whole 1D array as the search term. If we want to search for a single cell value within that array, we must specify which row we want to look in.
This means we use 2dArray[0].indexOf("Search Term") if our search term is not an array. Doing this specifies that we want to look in the first "row" in the array.
If we were looking at a 3x3 cell range and we wanted to search the third row we would use 2dArray[2].indexOf("Search Term")
The script below gets the current row in the spreadsheet and turns it into an array. It then uses the indexOf() method to search that row for "Search Term"
//This function puts the specified row into an array.
//var getRowAsArray = function(theRow)
function getRowAsArray()
{
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Get the current spreadsheet
var theSheet = ss.getActiveSheet(); // Get the current working sheet
var theRow = getCurrentRow(); // Get the row to be exported
var theLastColumn = theSheet.getLastColumn(); //Find the last column in the sheet.
var dataRange = theSheet.getRange(theRow, 1, 1, theLastColumn); //Select the range
var data = dataRange.getValues(); //Put the whole range into an array
Logger.log(data); //Put the data into the log for checking
Logger.log(data[0].indexOf("Search Term")); //2D array so it's necessary to specify which 1D array you want to search in.
//We are only working with one row so we specify the first array value,
//which contains all the data from our row
}
If someone comes across this post you may want to consider using the library below. It looks like it will work for me. I was getting '-1' return even when trying the examples provide (thanks for the suggestions!).
After adding the Array Lib (version 13), and using the find() function, I got the correct row!
This is the project key I used: MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T
And the references:
https://sites.google.com/site/scriptsexamples/custom-methods/2d-arrays-library#TOC-Using
https://script.google.com/macros/library/d/MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T/13
Hopefully this will help someone else also.
I had a similar issue. getValues() seems to be the issue. All other methods were giving me an indexOf = -1
I used the split method, and performed the indexOf on the new array created. It works!
var col_index = 1;
var indents_column = main_db.getRange(1,col_index,main_db.getLastRow(),1).getValues();
var values = String(indents_column).split(","); // flattening the getValues() result
var indent_row_in_main_db = values.indexOf(indent_to_edit) + 1; // this worked
I ran into the same thing when I was using
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues();
as I was expecting foo to be a one dimensional array. On research for the cause of the apparently weird behavior of GAS I found this question and the explanation for the always two dimensional result. But I came up with a more simple solution to that, which works fine for me:
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues().flat();
My goal is to take in a CSV file which contains approximately 4 million records and process each record while scrubbing the data of a particular field. The scrubbing process we have actually creates a reversible hash but is a time consuming process (almost 1 second). What I would like to do since there are only about 50,000 unique values for that field is to set them as properties of an object. Here is a pseudo example of how the object will be built. You can see that for duplicates I plan to just overwrite the existing value (this is to avoid having to loop through some if based search statement.
var csv = require('csv');
var http = require('http');
var CBNObj = new Object;
csv()
.fromPath(__dirname+'/report.csv',{
columns: true
})
.transform(function(data){
CBNObj[data['Field Value']] = data['Field Value'];
});
console.log(CBNObj);
This should create my object something like this.
myObj['fieldValue1'] = 'fieldValue1'
myObj['fieldValue2'] = 'fieldValue2'
myObj['fieldValue3'] = 'fieldValue3'
myObj['fieldValue1'] = 'fieldValue1'
myObj['fieldValue1'] = 'fieldValue1'
I have looked over some good posts on here about iterating over every property in an object (like this one Iterating over every property of an object in javascript using Prototype?) but I am still not exactly sure how to acccomplish what I am doing. How can I then take my object with 50k properties and essentially dump the values into an array so that I can end up with something like this?
myArray = ['fieldVaue1','fieldVaue2','fieldVaue3']
EDIT: I could also use some assistance on the first part here because I am getting a null value or undefined when I try and set the object properties. I also still need help then traversing through the object properties to build my array. Any help would be greatly appreciated.
You know that the keys of your object are the unique values you want. You just need an array. In node.js you can use Object.keys().
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
It's a standard way to take all the keys of an object (that aren't provided by the prototype chain) and put them into an array. So your example looks like this.
var csv = require('csv');
var AcctObj = new Object();
var uniqueArray;
csv()
.fromPath(__dirname+'/report.csv',{
columns: true
})
.on('data',function(data){
AcctObj[data['Some Field Value']] = data['Some Field Value'];
})
.on('end', function(){
uniqueArray = Object.keys(AcctObj);
});
Object.keys also does the hasOwnProperty check internally, so it's similar to the answer by #DvideBy0. It's just one step to the array you want.
var csv = require('csv');
var AcctObj = new Object();
csv()
.fromPath(__dirname+'/report.csv',{
columns: true
})
.on('data',function(data){
AcctObj[data['Some Field Value']] = data['Some Field Value'];
})
.on('end', function(){
for(var prop in AcctObj) {
if(AcctObj.hasOwnProperty(prop))
//Do something here....
}
});