SORT script works for owner but not editors - google-apps-script

I've written script to sort a google sheet by column A, and then take you to the cell next to the one you just edited, and I have it working perfectly for me the owner, but for editors, nothing seems to work starting from the "sort" function.
I assume this means some part of it is an installable trigger? but as far as I can tell I've made it all with simple triggers.
function onEdit(e) {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var cell = sheet.getActiveCell();
var NEXTcell = sheet.getActiveRange().offset(0,1);
var range = e.range;
var columnOfCellEdited = range.getColumn();
var sheet = spreadsheet.getActiveSheet();
// When Column A edited
if (columnOfCellEdited === 1) {// Column 1 is Column A
//Set marker
NEXTcell.setValue('sorting');
// Sort whole sheet excluding first 2 rows by Column A.
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).activate();
spreadsheet.getActiveRange().offset(2, 0, spreadsheet.getActiveRange().getNumRows() - 2).sort({column: 1, ascending: true});
// Find original cell after sorting
var rowoffset = 3;
var rng = sheet.getRange(rowoffset, 2, 600);
var data = rng.getValues();
var search = "sorting";
for (var i=0; i < data.length; i++) {
if (data[i][0] == search) {
data[i][0] = "";
rng.setValues(data);
var foundcell = sheet.getRange((i+rowoffset), 2);
foundcell.activate();
break;
}
}
}
}

So Stackdriver logs for-the-win.
Turns out protections I hadn't considered are the culprit.

Related

Auto Archive Schedule; Time-based Trigger

I have been asked to create a live Google Sheets Spreadsheet to track the work schedule at our yard. I have no experience with a script but found out I could program my sheet instead of hiding formulas and it would yield a cleaner result. I have been able to make the sheet organize itself and I was able to make it Archive manually (onEdit). What I'm looking for is to have it automatically run the code at 1 am so when we arrive at work it archives based on a cell value in a certain column.
This is an example of my onEdit script that works, but when someone is trying to check off the "YES" column there is some lag and can cause the wrong cell to be checked, which I then manually correct.
function onEdit() {
var sheetNameToWatch = "Schedule";
var columnNumberToWatch = 28;
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
So this code runs at 100% failure but saves and executes, and I honestly don't know why. Could be I misunderstand the values I need to insert after the "function" area. I did have this setup with an "Auto Archive" trigger that created a menu button with a "Run" option on the sheet, but when you click that it only does the last row with "Yes" in column 28 (every press of the button will move 1 row until all rows are moved) and the button won't work for the other users of the sheet.
function createTrigger() {
ScriptApp.newTrigger("Move Archive") //Move Archive is the name of the script
.timeBased()
.everyMinutes(1) // only set to 1 minute for testing, I can change this out for a daily timer
.create();
}
function myFunction() {
var sheetNameToWatch = "Schedule"; // "schedule" is the sheet we enter info on
var columnNumberToWatch = 28; //Column is "AB"
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive"; //"Archive is the sheet the info is sent to"
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange); // I get its programmed for last row with "Yes" here, unsure on how to change this.
sheet.deleteRow(range.getRow());
function myfunction() {
ScriptApp.deleteTrigger("Move Archive"); // could have wrong value here
}
}
}
All I want is the sheet to "Archive" based on a "Yes" value in Column 28 (AB). I want every row with that column value to Archive at 1 am automatically. Any help is appreciated. If someone even wants to recommend a book or digital instruction for beginners that would be great.
You have four project files each containing scripts of the same name, performing the same or similar tasks. You are experiencing lagging because you have multiple simple and installable scripts of the same name. Essentially, they are all trying to execute at the same time.
The following answer should be considered as one possible solution to your situation.
The elements of this script are:
There is a single project file. Unnecessary project files have been deleted.
There is a single function.
The function is a single "simple" trigger (onEdit(e)) which takes advantage of the various event objects returns by onEdit. There are no installable triggers, and any/all installable triggers have been deleted.
The function updates the "Schedule" and "Archive" sheets as described in the question; and then sorts both the "Schedule" and "Archive" sheets.
If there is a change on the "Railcars" sheet, the function sorts that sheet.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var testrange = e.range;
var testsheet = testrange.getSheet();
var testsheetname = testsheet.getSheetName();
var testrow = testrange.getRow();
var testcolumn = testrange.getColumn();
var testvalue = e.value;
var testsheetLC = testsheet.getLastColumn();
var testsheetRange = testsheet.getRange(testrow,1,1,testsheetLC);
//Logger.log("DEBUG: the test sheet range is "+testsheetRange.getA1Notation());
//Logger.log("DEBUG: Range: "+testrange.getA1Notation());
//Logger.log("DEBUG: The row is "+testrow+", and the column is "+testcolumn);
//Logger.log("DEBUG: The spreadheetsheet is "+e.source.getName()+", the sheet name is "+testsheet+", the range = "+testrange.getA1Notation()+", and the new value = "+testvalue);
//Logger.log(JSON.stringify(e));
// Copy/Paste to Schedule/Archive
var sheetNameSchedule = "Schedule";
var colNumberSchedule = 28;
var valueSchedule = "Yes";
var sheetNameArchive = "Archive";
// Sort Schedule
var sortSchedule = [{column: 1, ascending: true},{column: 2, ascending: true},{column: 7, ascending: false}];// date // Appt (time) // Type (Out/In/RR)
// Sort Railcars
var sheetNameRailcars = "Railcars";
var sortRailcars = [{column: 1, ascending: true}];
if (testsheetname === sheetNameSchedule && testcolumn === colNumberSchedule && testvalue === valueSchedule){
// this is a match
// Logger.log("DEBUG: this was a match");
// copy/paste to archive
var targetSheet = ss.getSheetByName(sheetNameArchive);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
//Logger.log("DEBUG: the target range is "+targetRange.getA1Notation());
testsheetRange.moveTo(targetRange);
testsheet.deleteRow(testrow);
// sort the Schedule Sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
// sort the Archive Sheet
var ATvals = targetSheet.getRange("A1:A").getValues();
var ATlast = ATvals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+ATlast+", and the last column = "+testsheetLC);
var sortrange = targetSheet.getRange(2,1,ATlast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
}
else if (testsheetname === sheetNameRailcars){
// sort the sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortRailcars);
}
}
You can set up an installable trigger to run this function at 1am every morning:
function scheduleToArchive() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var schedule = sheet.getSheetByName("Schedule");
var archive = sheet.getSheetByName("Archive");
var scheduleData = schedule.getRange(2, 1, (sheet.getRange('A:A').getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()), 28);
for (row = 2; row <= scheduleData.getNumRows(); row++){
var data = schedule.getRange(row, 1, 1, 28).getValues();
if (data[0][27] == 'Yes'){
archive.getRange((archive.getLastRow() + 1), 1, 1, 28).setValues(data);
}
schedule.getRange(row, 1, 1, 28).clear();
}
}
In your spreadsheet, when getDataRange() is run on the sheet named 'Schedule', it returns row 631 as the last row with data even though there is only data in the first 20 rows, so to get around that I've used SpreadsheetApp.Direction.DOWN instead and run that on column A.

Concatenating columns from many sheets in a single sheet

I would like to retrieve the data from many sheets into one single sheet.
I have 13 columns titles in my sheets, so I only take the data from the second row of all sheets.
For example, I have 3 sheets whose name are "FR", "UK", "DE", and "Master".
My columns are Country, Name, Month Usage, Model, Machine.
from Row 2, I have plenty of data for "FR","UK","DE".
"Master" has only the columns names.
What I want to merge all the data in a sheet called "Master".
So I took a code from Youtube "Combine one sheet into one" and the guy who has done the video made the code to retrieve data for 3 columns.
It actually does retrieve data from my 3 columns.
function combineData() {
var masterSheet = "Master";
var ss =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(masterSheet);
var lc = ss.getLastColumn();
var lr = ss.getLastRow();
// ss.getRange(2,1,lr-1,lc).clearContent();
var labels = ss.getRange(1, 1, 1, lc).getValues()[0];
labels.forEach(function(label, i) {
var colValues = getCombinedColumnValues(label, masterSheet);
ss.getRange(2, i + 1, colValues.length, 1).setValues(colValues);
})
function getCombinedColumnValues(label, masterSheetName) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var colValues = [];
for ([i, sheet] in sheets) {
var sheetName = sheet.getSheetName();
if (sheetName !== masterSheetName && sheetName !== "UID") {
var tempValues = getColumnValues(label, sheetName);
colValues = colValues.concat(tempValues);
}
}
return colValues;
}
function getColumnValues(label, sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var colIndex = getColumnIndex(label, sheetName);
var numRows = ss.getLastRow() - 1;
var colValues = ss.getRange(2, colIndex, numRows, 1).getValues(); // for name, index =2 but replacing by colIndex says "startong column too small)
return colValues;
}
function getColumnIndex(label, sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var lc = ss.getLastColumn();
var lookupRangeValues = ss.getRange(1, 1, 1, lc).getValues()[0];
var index = lookupRangeValues.indexOf(label) + 1;
return index;
}
};
I thought the code was dynamic to the number of columns present in my Master sheet, but it isn't. My error while compiling is "The starting column of the range is too small"
Does anyone has an idea to fix the bug?
Thanks.
Get Data from all Sheets
function getDataFromAllSheets() {
var excl=['Master'];//Sheets to exclude
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Master');//data destination
sh.getRange(2,1,sh.getLastRow(),sh.getLastColumn()).clearContent();//clears old data but leaves headers
var shts=ss.getSheets();
for(var i=0;i<shts.length;i++) {
if(excl.indexOf(shts[i].getName())>-1) {//does not collected data from excluded sheets
var vA=shts[i].getDataRange().getValues();//get sht[i] data
for(var j=1;j<vA.length;j++) {//skips first line
sh.appendRow(vA[j]);//appends all rows after first line
}
}
}
}

Move Rows From Multiple Tabs With a Historic Date in Column A to a Single Tab

I have a spreadsheet with multiple tabs all with a date in column A.
I need a script that I can run using a trigger each night to move all rows with a historic date in Column A to a single tab for historic rows.
I have been successful in getting historic rows in my first sheet to move to the historic tab however I cannot seem to make the script work for multiple tabs.
function HistoricDates() {
SHEET_NAME = "Area1" || "Area2" || "Area3"||"Area4";
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Sheet = ss.getSheetByName(SHEET_NAME);
var PastSheet = ss.getSheetByName("Historic Sheet");
var lastColumn = Sheet.getLastColumn();
// Check all values from sheets
for(var i = Sheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = Sheet.getRange(i, 1).getValue(); //Dates in column 1
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = Sheet.getRange(i, 1, 1, Sheet.getLastColumn()).getValues();
PastSheet.getRange(PastSheet.getLastRow() + 1, 1, 1, Sheet.getLastColumn()).setValues(rangeToMove);
Sheet.deleteRow(i);
}
}
}}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}
The expected result would be for all historic rows in Area 2,3 & 4 to move to move to the single historic tab.
My spreadsheet with script is available on the following link:
https://docs.google.com/spreadsheets/d/1WiZWok4onddTErdAxlWmU82KRSGfVJr5wi1p-rlbY5E/edit?usp=sharing
The way you defined SHEET_NAME, it will always be "Area 1". You can test this.
function test() {
SHEET_NAME = "Area1" || "Area2" || "Area3"||"Area4";
Logger.log(SHEET_NAME);
}
Instead, SHEET_NAME should be an array, and then you need to loop through that array. Below I've included a sample for how to define that array and get the sheets.
function loopThroughSpecificSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNames = ["Area1", "Area2", "Area3", "Area4"];
for (var j in sheetNames) {
var sheet = ss.getSheetByName(sheetNames[j]);
// Do other stuff
}
}
Assuming that the rest of your code works correctly (I haven't analyzed it), your modified script would look like this:
function HistoricDates() {
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastSheet = ss.getSheetByName("Historic Sheet");
var sheetNames = ["Area1", "Area2", "Area3", "Area4"];
for (var j in sheetNames) {
var sheet = ss.getSheetByName(sheetNames[j]);
var lastColumn = sheet.getLastColumn();
// Check all values from sheets
for(var i = sheet.getLastRow(); i > 0; i--) {
// Check if the value is a valid date
var dateCell = sheet.getRange(i, 1).getValue(); //Dates in column 1
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today) {
var rangeToMove = sheet.getRange(i, 1, 1, sheet.getLastColumn()).getValues();
pastSheet.getRange(pastSheet.getLastRow() + 1, 1, 1, sheet.getLastColumn()).setValues(rangeToMove);
sheet.deleteRow(i);
}
}
}
}
}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}

Google Apps Script : Insert x rows after every cells in range

I'm trying to add x blank rows after every no blank cells in a range.
I use a loop but with my loop, only the first cell is affected.
Here, my code :
I think I know where I'm wrong (I need to loop in sheet.insertRowsAfter(i, 5); but I can not do it...)
Thank you !
function insertRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("test");
var sourceRange = sheet.getRange(10, 1, sheet.getLastRow());
var sheetData = sourceRange.getValues();
var numRows = sourceRange.getNumRows();
Logger.log(numRows);
for (var i=10; i < numRows; i++) {
sheet.insertRowsAfter(i, 5);
}
}
In your case we need to start at the end of the range and work our way up, adding rows where appropriate. The code below will do this and add a 5 rows below any row which has data, even adding rows if there is a blank row following the one with data. You could pass startRow to the function if you wish for more flexibility
function insertRows() {
var startRow = 10;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("test");
var sourceRange = sheet.getRange(startRow, 1, sheet.getLastRow());
var sheetData = sourceRange.getValues();
var numRows = sourceRange.getNumRows() - startRow;
// Logger.log(numRows);
for (var i=numRows; i > -1; i--) {
if (sheetData[i].join("")) {
sheet.insertRowsAfter(i + startRow, 5);
}
}
}
As a general comment: You are retrieving more rows as necessary as sheet.getLastRow() gets the last row number with data and the use in getRange() is telling the app to pull that number of rows as opposed to that number minus the starting position.
Try the below. This will add 3 rows when there is a no blank cell.
function insertRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("test");
var sourceRange = sheet.getRange(1, 1, sheet.getLastRow());
var sheetData = sourceRange.getValues();
for (var i=0; i < sheetData.length; i++) {
if(sheetData[i][0]!=""){
sheet.insertRowsAfter(i+1, 3);
sheetData = sheet.getRange(1, 1, sheet.getLastRow()).getValues();
}
}
}

Google Spreadsheet, Checking the name of the sheet with a cell in a sheet

I have a Google spreadsheet with 3 sheets.
In the first sheet I have a resume of the two other sheet. This sheet has a column "C" where the name of the sheet corresponding to the resume is written.
Now I want a script that checks the name of the sheets and color the corresponding cell in column "C" into red if the name matches with the one in column "C".
I have written this code but doesn't seem to be working!
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var columnC = sheet.getRange (1, 3, sheet.getLastRow(), 1);
var Cvalues = columnC.getValues();
var allSheets = ss.getSheets();
var allSheetNames = new Array();
for (var i = 0; i < allSheets.length; i++)
{
allSheetNames.push(allSheets[i].getName());
}
for (var j=0 ; j < Cvalues.length; j++)
{
if (Cvalues[j][0] == allSheets) {
sheet.getRange( j, 1, 1, 1).setBackgroundColor('red');}
}
}
Problem here:
if (Cvalues[j][0] == allSheets) {
Your allSheets is an array, so the value in Cvalues[j][0] can never be equal to it. What you probably want it to determine if Cvalues[j][0] appears anywhere in allSheets. Here's how you'd do that:
if (allSheets.indexOf(Cvalues[j][0]) !== -1) {
...