i am trying to make a function that take a column from spreadsheet A and paste it as a row on spreadsheet B (transposed), the function works as expected from the apps script editor, but when i try to call it from it's linked spreadsheet i get this error: "Exception: You do not have permission to call SpreadsheetApp.openByUrl. Required permissions: https://www.googleapis.com/auth/spreadsheets (line 6)."
this is the code for the function:
function transposeColumn(colNum) {
var srcSheet = SpreadsheetApp.openByUrl("mySRCsheetID");
srcLastRow = srcSheet.getLastRow();
srcSheet = srcSheet.getDataRange().getValues();
console.log(srcSheet[0][colNum - 1]);
dataToCopy = [];
for (var i = 0; i < srcLastRow; i++) {
if (srcSheet[i][colNum - 1] == '') {
break;
} else {
dataToCopy[i] = srcSheet[i][colNum - 1];
}
}
console.log(dataToCopy);
dataLength = dataToCopy.length;
console.log(dataLength)
var destSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var destCell = destSheet.getActiveCell();
var cellCol = destCell.getRow();
var cellRow = destCell.getColumn();
for (var i = 0; i < dataLength; i++) {
destSheet.getRange(cellRow, cellCol).setValue(dataToCopy[i]);
cellCol++;
}
}
i need this function to run properly when i call it from the destination spreadsheet.
An example of simplest solution is here:
function onOpen() {
SpreadsheetApp.getUi().createMenu('⚙ Scripts')
.addItem('Get column', 'get_column')
.addToUi();
}
function get_column() {
var s_src = SpreadsheetApp.openByUrl("your_url");
var col = s_src.getRange("A1:A").getValues().flat();
var row = SpreadsheetApp.getActiveSheet().getRange(1,1,1,col.length);
row.setValues([col]);
}
After reload you will get the menu
Perhaps you need to run the function onOpen() from Script Editor first time to get permissions.
This script copies column A from source spreadsheet and paste it as a row 1 on active sheet.
If you need to get another columns (by its index or letter) it can be done. Let me know. For example you can put the index (or letter) of required column in current cell and script can read the number (or letter) from current cell. Or you can show a prompt message to input the index (or letter) of required column.
Related
Beware, I may be overthinking this.
I keep getting into cyclic thought loops when I'm trying to figure out what to do in this situation, So I will try to explain my thinking and where I am at.
Form is filled out on Google Sheets
Form replies are added to the main Form sheet in the "form responses tab"
Code actives, checking to see if the form was filled correctly (columns A and B match)
if they match, it finds the respective google spreadsheet ID that that row needs to go to, by looking at the directory tab.
That item is then sent over to the appropriate list, which is in it's own sheet
This continues for the rest of the rows of the Main QA Forms Responses tab, until all rows have been checked and there are no more entries.
I've been trying to understand this for hours on end, but might be approaching this all from the wrong angle.
As of right now, this is how far i've gotten in the code:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveRange();
var columnSearchNum = 3;
var columnDatastarts = "C";
var formSheetName = "QA Form Responses";
var directorySheetName = "Program Directory";
var matchingProgramSheetIDColumn = 1;
if(s.getName() == formSheetName && r.getColumn(0) == r.getColumn(1)) {
var sourceRow = r.getRow();
var matchingProgram = sourceRow.getRange(0,0).getValue();
var matchingProgramSheetID = s.getName(Programdirectory)....//[code needed1]
//^^^^ I need a line here to pull matching the data inSheetID column where Matching program's string is
//from this code line, Go to that 'program's sheet'
var programSheet = ss.getSheetByID(matchingProgramSheetID);
var programSheetNumRows = programSheet.getLastRow();
//console.log(programSheetNumRows);
var formSheetNumColumns = s.getLastColumn();
var targetRange = programSheet.getActiveRange()
var targetValue = +s.getRange(columnDatastarts+sourceRow).getValue()
//console.log(targetValue);
var programSheetRange = programSheet.getRange(1,columnSearchNum,programSheetNumRows,1);
//console.log(programSheetRange.getNumRows() +" " +programSheetRange.getNumColumns() + " " + programSheetRange.getValues());
var targetRow = findIndex(programSheetRange.getValues(), targetValue);
//console.log(targetRow);
var target = programSheet.getRange(targetRow, 1);
s.getRange(sourceRow, 2, 1, formSheetNumColumns).moveTo(target);
;
}
}
function findIndex(array, search){
//console.log(array);
if(search == "") return false;
for (var i=0; i<array.length; i++){
//console.log("comparing " + +array[i] + " to "+ +search);
if (+array[i] == +search){
return i+1;
}
}
return -1;
}
You want to find a match for a value on the Form Responses sheet with values in a given column on another sheet, and then return the value in the cell adjacent to the matching cell.
There are probably many ways to do this, but the Javascript method indexOf is an obvious choice if using a script. The following is untested, but the logic is sound.
Insert at [code needed1]
// define the Program sheet
var progsheet = ss.getSheetByName(directorySheetName)
// define the first row of data on the program sheet
var firstRowofData = 3
// get the data for columns one and two of the program sheet.
// starting row=3, starting column=1, number of rows = lastrowminus first row plus 1, number of columns = 2
var progsheetdata = progsheet.getRange(firstRowofData,1,progsheet.getLastRow()-firstRowofData+1,2).getValues()
// get Program manager Column
var ProgManager = progsheetdata.map(function(e){return e[0];})
// search the Program manager Column for the first instance of the matching program value
// indexOf returns index if found, or -1 if not found
var result =ProgManager.indexOf(matchingProgram);
if (result == -1){
// couldn't find a matching program
// do something to quit/error message
return
}
else
{
// the id will be in the row returned by IndexOf and in the adjacent column to the right.
var id = progsheetdata[result][1]
}
Only lastRow1 shows me the wrong value, if rows are hidden. How to ignore hidden rows?
function x() {
var s = SpreadsheetApp.getActive().getActiveSheet();
var lastRow1 = s.getLastRow();
var lastRow2 = s.getActiveRange().getLastRow();
Browser.msgBox(lastRow1);
Browser.msgBox(lastRow2);
if (lastRow1 == lastRow2) {
// Do something.
}
}
I believe your goal as follows.
When the following sample situation, you want to retrieve 5 as lastRow.
You want to achieve this using Google Apps Script.
For this, how about this answer?
Modification point:
In this case, lastRow is retrieved with isRowHiddenByUser.
Modified script:
function x() {
var s = SpreadsheetApp.getActive().getActiveSheet();
var lastRow = 0;
for (var i = s.getLastRow(); i > 0; i--) {
if (!s.isRowHiddenByUser(i)) {
lastRow = i;
break;
}
}
console.log(lastRow); // or Browser.msgBox(lastRow);
}
Note:
If you want to retrieve lastRow from the sheet with the hidden rows by the basic filter, please modify as follows.
From
if (!s.isRowHiddenByUser(i)) {
To
if (!s.isRowHiddenByFilter(i)) {
References:
isRowHiddenByUser(rowPosition)
isRowHiddenByFilter(rowPosition)
I am trying to fix a code I found online. My goal is that once the Summary tab, column I is edited with the drop-down "approved" for the sheet to send an email to the person on the name in column D.
The email is found in the range tab though. This is what I have so far...
var admin_email='taniapeche#gmail.com';
function triggerOnEdit(e)
{
sendEmailOnApproval(e);
}
function checkStatusIsApproved(e)
{ var sheet = SpreadsheetApp.getActive().getSheetByName('Summary');
var range = e.range;
if(range.getColumn() <= 9 &&
range.getLastColumn() >=9 )
{
var edited_row = range.getRow();
var status = SpreadsheetApp.getActiveSheet().getSheetName('Summary').getRange(edited_row,9).getValue();
if(status == 'Approved')
{
return edited_row;
}
}
return 0;
}
function sendEmailOnApproval(e)
{ var sheet = SpreadsheetApp.getActive().getSheetByName('Range');
var approved_row = checkStatusIsApproved(e);
if(approved_row <= 0)
{
return;
}
sendEmailByRow(approved_row);
}
function sendEmailByRow(row)
{
var values = SpreadsheetApp.getActiveSheet().getSheetName('Range').getRange(row,1,row,5).getValues();
var row_values = values[0];
var mail = composeApprovedEmail(row_values);
//SpreadsheetApp.getUi().alert(" subject is "+mail.subject+"\n message "+mail.message);
MailApp.sendEmail(admin_email,mail.subject,mail.message);
}
function composeApprovedEmail(row_values)
{
var first_name = row_values[1];
var last_name = row_values[2];
var email = row_values[3];
var message = "The following mileage is approved: "+first_name+" "+last_name+
" email "+email;
var subject = "Mileage approved "+first_name+" "+last_name
return({message:message,subject:subject});
}
This is how to sheet looks:
https://docs.google.com/spreadsheets/d/1lWORvuwAHducEIiL-VVidJ-wjujE344udPbWCZpE1kw/edit?usp=sharing
Thanks for the help :)
First of all, because you want the script to send an email (an action which requires your authorization), you have to install the edit trigger, either manually or programmatically. If you do it programmatically, you can install the trigger by running this function once:
function createTriggerOnEdit(e) {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("sendEmailOnApproval")
.forSpreadsheet(ss)
.onEdit()
.create();
}
As a result of this, the function sendEmailOnApproval will fire every time the spreadsheet is edited. This function could be something along the following lines (check inline comments for detailed explanation):
function sendEmailOnApproval(e) {
// Get the edited range and sheet using the event object:
var range = e.range;
var editedSheet = range.getSheet();
var textToSearch = "Approved"; // Set which value will cause the email to be sent
// Check that edited cell is in column I, its value is "Approved" and its sheet is "Summary":
if (range.getColumn() === 9 && range.getValue() === textToSearch &&
editedSheet.getName() === "Summary") {
var rowIndex = range.getRow(); // Get index of the edited row
var name = editedSheet.getRange(rowIndex, 4).getValue(); // Get corresponding name in column D
var rangeValues = e.source.getSheetByName("Range").getDataRange().getValues(); // Get values in sheet "Range"
// Iterate through the rows in "Range", looking for the name retrieved from sheet "Summary"
for (var i = 0; i < rangeValues.length; i++) {
var rowValues = rangeValues[i];
if (name === rowValues[0]) { // Check if name matches the one in column D from "Summary"
var mail = composeApprovedEmail(rowValues); // Compose email (your function)
MailApp.sendEmail(admin_email, mail.subject, mail.message); // Send email
return; // End execution so that the script stops iterating through the rows in "Range"
}
}
}
}
Notes:
The function composeApprovedEmail is called in this sample. It's the same as the one you provided. The rest of functions you provided are not used.
Reference:
Installable Triggers
Event Objects: Edit
I want onFormSubmit(e) to be my main function trigger and within that I want onEdit(e) to be nested. Basically, no matter, the trigger will run onFormSubmit but it will do others within the onEdit if there is any edit, if there isn't then it will do something else.
I can't see to understand and make it work.
My script triggers shows onFormSubmit as the only function and onEdit is not in the dropdown.
function onFormSubmit(e){
ScriptApp.newTrigger("onEdit").timeBased().after(60000).create();
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet().getRange('SpeedVSD');
var sheet = ss.getSheetByName("Responses 1");
var row = ss.range.getRow();
var col = ss.range.getColumn();
if (col >= ss.getColumn() && col <= ss.getLastColumn() && row >= ss.getRow() && row <= ss.getLastRow()){
console.log("You edited a Cell within Range");
}
}
edit: Managed to get my lastRow value. However, I am still looking to get a command that can get the lastRow value for all the columns instead of manually doing it.
edit: Using a FOR Loop helps with collating the values.
//This is to get the Last Row on Column 2 value.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheetByName('FIRST');
var row = sheets.getLastRow();
for(var i = 1; i <= sheets.getLastColumn(); i++){
var myID = sheets.getRange(row, i).getValue();
}
console.log("Row Number: "+row);
console.log("Content of last Row: "+myID);```
If you want the onEdit() to run always, you just create it as a separate function. then you can call it from the onFormSubmit(), like this:
function onFormSubmit(e){
//does something you need...
onEdit();
}
onEdit(e){
//do the onEdit code...
}
The only problem with this is that the event e for onFormSubmit() is different than the one for onEdit(), so working with events might not be the best idea. However, calling one function from the other would fun just like with any other function.
function onFormSubmit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheetByName('FIRST');
var row = sheets.getLastRow();
var myIDCol2 = 2;
var myIDCol3 = 3;
var myID2 = sheets.getRange(row, myIDCol2).getValue();
var myID3 = sheets.getRange(row, myIDCol3).getValue();
console.log("Speed Before Trigger Value: "+myID2);
console.log("Voltage Before Trigger Value: "+myID3);
ScriptApp.newTrigger("responsechange").timeBased().after(60000).create();
}
function responsechange(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheetByName('FIRST');
var row = sheets.getLastRow();
var myIDCol2 = 2;
var myIDCol3 = 3;
/*for(var i = 1; i <= sheets.getLastColumn(); i++){
console.log("Content of last Row: "+myID);
}*/
var myID2 = sheets.getRange(row, myIDCol2).getValue();
var myID3 = sheets.getRange(row, myIDCol3).getValue();
var template1 = HtmlService.createTemplateFromFile("speed1");
var template2 = HtmlService.createTemplateFromFile("voltage");
template1.speed1 = myID2;
template2.voltage = myID3;
console.log("Speed After Trigger Value: "+myID2);
console.log("Voltage After Trigger Value: "+myID3);
if((myID2 >=100) || (myID2 <= 50)){
MailApp.sendEmail("someone#gmail.com","Out of Range Notification Speed","",{htmlBody: template1.evaluate().getContent()});
}
if((myID3 >=100) || (myID3 <= 50)){
MailApp.sendEmail("someone#gmail.com","Out of Range Notification Voltage","",{htmlBody: template2.evaluate().getContent()});
}
}
With this, I managed make it work whereby on form submit, lets say the values are below 50 and above 100, it will trigger an email after the time-based trigger. I also tried within the time-based trigger, I edited the values to be within the range and it did not send an email. However, the only problems now is, if there are many triggers, it will stop the trigger by saying
This script has too many triggers. Triggers must be deleted from the script before more can be added.
But on the bright side, I managed to get the last value submitted to have it checked if it was edited or not.
I searched the internet and I can't find a response to this nor the documentation for it.
I need to dynamically generate Google forms questions with data from a Google spreadsheet using app script, but I don't know how to reference and read a spreadsheet.
In your spreadsheet select Tools > Script Editor and adapt this to your needs:
/**
After any change in the sheet, update the combobox options in the Form
*/
function onChange(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var range = sheet.getDataRange();
var values = range.getValues();
var comboValues = []; // <-- cheddar will go here
// in this example we are interested in column 0 and discarding row 1 (the titles)
for (var i = 1; i <= values.length; i++) {
var v = values[i] && values[i][0];
v && comboValues.push(v)
}
// Sort the values alphabetically, case-insensitive
comboValues.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
Logger.log(comboValues);
// Use your form ID here. You can get it from the URL
var form = FormApp.openById('<my-form-id>');
/*
Uncomment this to display the item IDs
and pick the one that you want to modify
var items = form.getItems();
for (i = 0; i < items.length; i++) {
Logger.log("ID: " + items[i].getId(), ': ' + items[i].getType());
}
*/
form.getItemById(807137578).asListItem().setChoiceValues(comboValues);
};
To debug, select the script in the combobox and click either "play" or "debug". The first time you will have to give it permissions to interact with your spreadsheet and form.
Once you are satisfied with the result, in the editor select Resources > Triggers for the active project and add this method to be triggered with any modification on the spreadsheet (on change, not on edit).
After this, your form options will be changed in real time after any change in your spreadsheet.
It's pretty straightforward, see here: https://developers.google.com/apps-script/guides/sheets#reading
You just need to open the sheet by its doc key, select the data and read the cells as a JS object.
Here is an example which works for me, pls kindly check:
function getSpreadsheetData(sheetId) {
// This function gives you an array of objects modeling a worksheet's tabular data, where the first items — column headers — become the property names.
var arrayOfArrays = SpreadsheetApp.openById(sheetId).getDataRange().getValues();
var headers = arrayOfArrays.shift();
return arrayOfArrays.map(function (row) {
return row.reduce(function (memo, value, index) {
if (value) {
memo[headers[index]] = value;
}
return memo;
}, {});
});
}
function makeOurForm() {
var sheetId='input_your_sheet_id'
getSpreadsheetData(sheetId).forEach(function (row) {
// Set your form template as follows
var formName=row.Name
// Create your form programmatically, each row means one form
var form = FormApp.create(formName)
form.setDescription('xxx');
var capitalizedName = row.Name.charAt(0).toUpperCase() + row.Name.slice(1);
form.addSectionHeaderItem().setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle('xxx')
.setChoices([
item.createChoice('xxx'),
]);
form.addParagraphTextItem().setTitle('xxx');
});
}
You can get your sheet Id from url, for example:
https://docs.google.com/spreadsheets/d/YourSheetId/edit#gid=0
Let me know if you have any further questions.