Permission problems when altering a spreadsheet - google-apps-script

I have this piece of code that should set a custom format number depending on the value of the cell.
function fixFormat() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("K1:K30");
var rows = range.getNumRows();
var cols = range.getNumColumns();
for(var row = 1; row <= rows; row++) {
for(var col = 1; col <= cols; col++) {
var cell = range.getCell(row, col);
var value = cell.getValue();
if (value > 1000) {
cell.setNumberFormat("#.###B");
} else {
cell.setNumberFormat("#.###");
}
}
}
}
When configured as a trigger (onChange) it does not run at all, when called as =fixFormat() I get the error: You don't have permission to call setNumberFormat.
I am the owner of the sheet
I have not blocked access to anything
I had the same script working on other sheets
What can I do to solve this?

Found the problem
After a lot of digging, the support page says:
Spreadsheet: Read only (can use most get*() methods, but not set*()). Cannot open other spreadsheets (SpreadsheetApp.openById() or SpreadsheetApp.openByUrl()).
So the solution (barely) is to use custom menus

Related

How to set the data rapidly in google apps script?

I'm a junior developer in the HR system.
Let's assume the below.
There are two sheets 'A' and 'B'.
I want to copy the contents of 'B' to 'A'.
So I selected the method using getRange, getValues, setValue.. etc.
The method was successful, but the processing speed failed.
The speed was very very slow.
How can I set the data more rapidly?
I wrote the code below.
Please help me with your advice!
function setList(){
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
fromSheet = spreadSheet.getSheetByName('B');
toSheet = spreadSheet.getSheetByName('A');
var arry = fromSheet.getRange('A1:M239').getValues();
Logger.log(arry);
var lastRow = fromSheet.getLastRow();
var lastCol = fromSheet.getLastColumn();
for (var j = 2; j< lastRow; j++){
for (var i = 1; i < lastCol; i++){
toSheet.getRange(usedRow + 1,i).setValue(fromSheet.getRange(usedRow + 1,i).getValue());
}
usedRow++;
}
}
You were on the right track at first with getValues() but then you did each cell individually with getValue()/setValue(). Since you want to copy all the values from fromSheet to toSheet just use 1 setValues().
function setList(){
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
fromSheet = spreadSheet.getSheetByName('B');
toSheet = spreadSheet.getSheetByName('A');
var arry = fromSheet.getRange('A1:M239').getValues();
Logger.log(arry);
array.shift(); // remove the headers
// you don't define usedRow so I'm assuming its the same row as fromSheet
toSheet.getRange(2,1,arry.length,arry[0].length).setValues(arry);
}
Reference
Sheet.getRange()
Range.getValues()
Range.setValues()
Best Practices

Google Apps Script: Prevent duplicate copies based on two columns

I am working with some colleagues who want the following to happen within a Google Sheet:
A Google Form contains a question that asks which Counselor a student is assigned to (among other questions)
Forms are submitted throughout the year by students
When a form is submitted, the form data goes into a Google Sheet in a Responses sheet
The Counselors would like a copy of each row to appear in another sheet within the main Sheet, based on the name of the Counselor
In their own sheets, each Counselor needs to be able to manipulate the data (sorting, highlighting rows, adding notes to the row/submission) ←hence a copy is needed instead of a query
I have the following script that copies the rows in the correct Counselor sheet, and does not copy a row into a Counselor sheet if it already appears. However, if a Counselor modifies anything in the row, the script will make a duplicate row (with the original data) the next time it is run, perhaps because it sees the modified row as not an exact match.
Is there a way to modify my script so it can check against a unique part of a row in the Responses sheet (the columns at indexes 0 and 1 together in the same row create a unique entry) in any part of a Counselor sheet before it creates a copy? In other words, it would not create a duplicate row if the Counselor modifies anything except for columns 0 and 1.
function copyData() {
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[4]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getDataRange();
var data = range.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm stuck at this point and am not sure how to proceed.
NOTE: I have code to add a button to the menu list for the Counselors to run this script as needed since the forms can be submitted at any time. Using "onFormSubmit" does not work because there is a potential for multiple students to submit the form at the same time, which I've seen can cause a row or two to not be copied over.
If I understand your question correctly, you want to find a way to avoid duplicated rows, even if you edit them.
In order to do that, you have to define a value for each row that won't change and that is unique. My suggestion would be the following :
Installable trigger with the function custom_onFormSubmit
In the function get Uid (unique ID), and add it to each row submitted
Edit your code in order to search duplicate only with this Uid
First, add this function your Google Form Apps Script:
//add unique ID at a defined column each time a google form is submitted
function custom_onFormSubmit(e){
var uuid = e.triggerUid;
//alternatily you can use:
//var uuid = Utilities.getUuid();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var row = range.getLastRow();
sheet.getRange(row, 10).setValue(uuid); //column 10 is for example, adapt to your need
}
------ EDIT: alternative function without trigger onFormSubmit, add this function before
function check_insert_uuid(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][10] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 10, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
------ END EDIT -------
Then you just have to edit your function copyData
FROM:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
TO:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[10] == row[10]) { //same example of column index 10
duplicate = true;
break;
}
}
References:
Installable Triggers
Google Form Events
Apps Script getuuid (Unique ID are not 100% unique in time and space, but will certainly answer your project)
Based on the help from #waxim-corp, here is the final script that accomplishes my goal:
function onOpen(e) {
let ui = SpreadsheetApp.getUi();
ui.createMenu("🤖 Copy Data 🤖")
.addItem("Let's Do This!", 'checkForID')
.addToUi();
};
function checkForID(){
var ss = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var range = ss.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][0] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 0, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
function copyData(){
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[5]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var rangeC = sheet.getDataRange();
var data = rangeC.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[0] == row[0]) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm sure it could be more efficient, but it works well.

How to automatically merge cells in Google Sheets upon creation of new Sheet

I am trying to figure out a way to make Google Sheets automatically merge Cells A1-C1 when a new sheet is created. My coworker and I have been trying to figure out the script that would make this happen, but everything we have tried only changes the previous Sheet we were working on, not the new one.
So far these are the two scripts we have tried, just to get some sort of result we are looking for:
function formatCells() {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var s = ss.getSheetByName('Combined')
var range = s.getDataRange()
var values = range.getValues();
for( var row = values.length -1; row >= 0; --row)
if (values[row][1] == 'Hello')
{s.getRange(row+1,1).mergeAcross();
}
}
and
function newSheetTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('newSheet')
.forSpreadsheet(ss)
.onChange()
.create();
}
function newSheet(e){
if (e.changeType == 'INSERT_GRID') {
SpreadsheetApp.flush();
SpreadsheetApp.getActiveSheet().getRange('A1:C1').merge();
}
}
Does anyone have an idea of where we went wrong?
The problem is that theonChange trigger is not able to detect the active sheet correctly
Retrieving the active sheet on trigger will always return you the first sheet, as you can easily verify with
function myFunction(e) {
Logger.log(e.changeType);
if(e.changeType=="INSERT_GRID"){
Logger.log(SpreadsheetApp.getActive().getActiveSheet().getName());
}
}
So you need to implement a workaround.
For example:
Strore the present sheet names in Script properties
When the trigger fires and the condition e.changeType=="INSERT_GRID" is fullfilled:
Compare the currently present sheet number to the one stored in script properties to evaluate either a new sheet has been inserted
If the sheet number increased - find the name of the new sheet with indexOf()
Merge cells on the new sheet and update the script properties
Code snippet:
var ss = SpreadsheetApp.getActive();
//run me once
function firstSetUp(){
var sheets = ss.getSheets();
var names = [];
for (var i = 0; i < sheets.length; i++){
names.push(sheets[i].getName())
}
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(names) );
}
//run me on trigger
function newSheet(e) {
if(e.changeType=="INSERT_GRID"){
var newSheets = ss.getSheets();
var oldSheetNames = JSON.parse(PropertiesService.getScriptProperties().getProperty("sheets"));
Logger.log(oldSheetNames);
var length = oldSheetNames.length;
Logger.log("length : " + length);
if (length != newSheets.length){
for (var i = 0; i < newSheets.length; i++){
if(oldSheetNames.indexOf(newSheets[i].getName()) == -1){
var newSheet = newSheets[i];
Logger.log(newSheet.getName());
newSheet.getRange('A1:C1').merge();
oldSheetNames.push(newSheet.getName());
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(oldSheetNames));
break;
}
}
}
}
}

Is there a script to delete a row based on a particular cell value

I'm trying to find a script for a button so that when I press it, if a certain cell contains any number that's less than 5, the row it's on is deleted. For instance, cell G13 is <5 so delete row.
I can't find anything online for this type of script - is this possible?
Small sample:
Delete a row if a cell value is <5:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var cell = sheet.getRange("G13");
var cellContent = cell.getValue();
if (cellContent < 5){
var row = cell.getRow();
sheet.deleteRow(row);
}
}
UPDATE:
Delete all rows where a cell value in a certain column is <5:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var columnG = sheet.getRange(1,7, sheet.getLastRow(), 1).getValues();
for (var i = 0; i < columnG.length; i++){
var cellContent = columnG[i][0];
if (cellContent < 5){
var row = i+1;
sheet.deleteRow(row);
}
}
}
I recommend you to study the Apps Script documentation to gain a deeper understanding.

Set Default value for column each time a new row is created

I have a Google form set up which puts the responses into a Google Spreadsheet, I have added a few extra columns to the sheet to help us monitor the status of each response. Is there a way I can set the default value for a column each time a new row is created? (i.e. each time a new response is submitted). Below is the code I have so far, but currently it only does it for one row.
function setDefault() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = ss.getLastRow();
for (var i = 3; i <= lastRow; i++)
{
sheet.getRange('A' + i).activate();
var value = sheet.getActiveCell().getValue();
while (value != "")
{
sheet.getActiveCell().offset(0, 5).activate();
sheet.getActiveCell().setValue("Unknown");
sheet.getActiveCell().offset(0, 1).activate();
sheet.getActiveCell().setValue("New");
}
}
};
I solved it using the following code and set the project trigger to "On form submitted" so that it runs each time a new response is submitted.
function setDefault() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = ss.getLastRow();
for (var i = 3; i <= lastRow; i++)
{
sheet.getRange('A' + i).activate();
sheet.getActiveCell().offset(0, 5).activate();
sheet.getActiveCell().setValue("Unknown");
sheet.getActiveCell().offset(0, 1).activate();
sheet.getActiveCell().setValue("New");
}
};
The method through last row might have troubles that the form submission isn't always the last row if you manually edit the spread sheet or if a user edit his response! That happens a the time ><"
I suggest another approach, try this:
function onFormSubmit(e){
var r = e.range.rowStart;
Logger.log(r);
}
}
Actually you can just log out the variable "e" to find out more submission informations ##b