Total beginner here so please dumb down your answers lol - How do I add a blank row to a specific row in google sheets that is being sorted without the row moving before I can edit it? I'm happy to change my system as needed.
Basically, I'm building an internal workflow program and have an auto-sort script for a list of jobs, when the jobs are completed the staff change row 1 from "A" to "Z" which pushes that row/job to the bottom of the sheet. However, the problem we have is when we add new work by inserting a row and start typing....the row moves before we can put data into it - it's a pain - what ideas does everyone have where I can improve this?
Thoughts / Ideas?
This is the sort script I have at the moment.
function onEdit(event){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Large")
var editedCell = sheet.getActiveCell();
var columnToSortBy = 1;
var tableRange = "A9:M100";
if(editedCell.getColumn() == columnToSortBy){
var range = sheet.getRange(tableRange);
range.sort( { column : columnToSortBy } );
}
}
I could find two possible solutions:
1- Use the cell's oldValue, it's value is undefined whenever you add a value to the cell for the first time. Using this solution will sort the table if you change the value a second time:
function onEdit(event){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Large")
var editedCell = sheet.getActiveCell();
var columnToSortBy = 1;
var tableRange = "A9:M100";
if(editedCell.getColumn() == columnToSortBy && event.oldValue != undefined){
var range = sheet.getRange(tableRange);
range.sort( { column : columnToSortBy } );
}
}
2- Sort only if the the cell is changed to Z, update the condition to:
if(editedCell.getColumn() == columnToSortBy && event.oldValue === "Z"){
Reference:
Event Objects
Related
Thank you for taking the time to look at this.
I have a sheet where I've cobbled together a script that moves a row on edit of a certain value, and then does a multi-sort of the involved sheets so that the row is in order on the new sheet. Works great.
However, now asking if I can make this work with filters. This is where things go wonky. When a filters is engaged, the sort function wrecks the filter when they move a row. Also, the filtered bracket shrinks, and though rows get sorted, and end up within the filtered bracket, others get pushed out, etc. It's a bit hard to explain, so here are two examples:
Script functioning in an unfiltered sheet: https://docs.google.com/spreadsheets/d/1Uwge7eS6DXJvbNi-7kRY9djg4Cq2r4QO9BeyNjnhkCg/edit?usp=sharing
Script functioning and wrecking filters: https://docs.google.com/spreadsheets/d/1fXJ8_5bqYCOpm6PlModT-GpymL8zrS0NXkhSYweZX6Y/edit?usp=sharing
(you can turn the filters off and on to reset it if you need to)
When a row is moved can it be sorted within the active filtered set? I'm not sure this is possible. Is it?
My work around would be to have the sorting functions execute onOpen so that they don't disrupt anyones filtered data, but this sheet has a fair number of users, and so I'm not sure if it's ever closed. Any other suggestions?
I appreciate your time and consideration. Thank you. I hope I can return the favour one day.
Here is the script:
SORT_DATA_RANGE = "A2:F99"
SORT_ORDER= [
{column: 4, ascending: true},
{column: 5, ascending: true}];
function onEdit(event) {
movethings1(event);
sortthings1()
sortthings2();
}
function movethings1(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(r.getColumn() == 2 && ( r.getValue() == "paid" || r.getValue() == "unpaid"
|| r.getValue() == "pending") ) {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet;
if(r.getValue() == "paid")
targetSheet= ss.getSheetByName("Paid");
else if(r.getValue() == "unpaid")
targetSheet= ss.getSheetByName("Unpaid");
else if(r.getValue() == "pending")
targetSheet= ss.getSheetByName("Unpaid");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);}
}
function sortthings1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Unpaid");
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);}
function sortthings2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Paid");
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);
ss.toast('done that thing you wanted');}
I have the following google apps script. It functions to look in my PENDING-IP tab and check the status (column G). If the status is "Screened - Ready for Review," then it moves the entire row of data to the SCREENED - READY FOR REVIEW tab.
My problem is this - the data in columns L, M, and N of the PENDING tab is a checkbox, but when it pushes it to the SCREENED tab, it changes it to TRUE or FALSE. Is there a way to modify my script to push the data with validation to retain the checkboxes? Thank you!
function screened() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('PENDING-IP'); //source sheet
var testrange = sheet.getRange('G:G'); //range to check
var testvalue = (testrange.getValues());
var csh = ss.getSheetByName('SCREENED - READY FOR REVIEW'); //destination sheet
var data = [];
var j =[];
//Condition check in G:G; If true copy the same row to data array
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] == 'Screened - Ready for Review') {
data.push.apply(data,sheet.getRange(i+1,1,1,25).getValues());
//Copy matched ROW numbers to j
j.push(i);
}
}
//Copy data array to destination sheet
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
//Delete matched rows in the source sheet
for (i=0;i<j.length;i++){
var k = j[i]+1;
sheet.deleteRow(k);
//Alter j to account for deleted rows
if (!(i == j.length-1)) {
j[i+1] = j[i+1]-i-1;
}
}
}
Try this
I haven't actually tested this except in a simpler situation
function screened() {
var ss=SpreadsheetApp.getActive();
var sheet=ss.getSheetByName('PENDING-IP');
var testrange=sheet.getRange(1,7,sheet.getLastRow(),1);
var testvalue=testrange.getValues();
var valids=testrange.getDataValidations();
var csh = ss.getSheetByName('SCREENED - READY FOR REVIEW'); //destination sheet
var data = [];
var valid = [];
var j =[];
var d=0;
for (var i=0;i<testvalue.length;i++) {
if (testvalue[i][0] == 'Screened - Ready for Review') {
data.push(sheet.getRange(i+1-d,1,1,25).getValues());//I am not sure but I could see having to put [0] at the end here
valid.push(sheet.getRange(i+1-d,1,1,sh.getLastColumn()).getDataValidations());//I am not sure but I could see having to put [0] at the end here
sheet.deleteRow(i+1-d++);//this should delete matching rows
}
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(valid);
}
What I've found is that you can treat the validations the same way you treat the data by replacing getValues() with getDataValidations() and setValues() with setValidations();
So I'm using this script (credit to Chicago Computer Classes) for populating dynamic data validation of a Google Sheets cell based on what the user entered in a different cell.
For example, if they enter the sport "Football" in one cell, the next cell has data validation for "CFB, CFL, or NFL" but if they enter "Basketball" in the first cell then the second cell's data validation changes to "ABL, CBB, NBA, or WNBA" for examples.
The script is working fantastic and you are welcome to play with the sheet here
However ... here's my problem:
I have an existing spreadsheet with 9000 rows of data. I would like to apply this new data validation scheme to this spreadsheet. The script is triggered with the onEdit() function which works great when you are entering things one row at a time. But if I try to copy and paste a whole bunch of rows in the first column, only the first row of the second column triggers the onEdit and gets the new data validation while all the other rows of the second column are unchanged. I've also tried to "Fill Down" or "Fill Range" on the first column and they have the same result where the first row in the selected range gets the new data validation but the rest of the selection is unchanged.
And while it would work just fine if I was manually entering rows, I really don't feel like doing that 9000 times :)
How do I modify the script to trigger the function with data that's copy/pasted or filled down?
Thanks!
Script here:
function onEdit(){
var tabLists = "Leagues";
var tabValidation = "2018";
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var datass = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(tabLists);
var activeCell = ss.getActiveCell();
if(activeCell.getColumn() == 6 && activeCell.getRow() > 1 && ss.getSheetName() == tabValidation){
activeCell.offset(0, 1).clearContent().clearDataValidations();
var makes = datass.getRange(1, 1, 1, datass.getLastColumn()).getValues();
var makeIndex = makes[0].indexOf(activeCell.getValue()) + 1;
if(makeIndex != 0){
var validationRange = datass.getRange(3, makeIndex, datass.getLastRow());
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0, 1).setDataValidation(validationRule);
}
}
}
You should use the event object, which will provide you with the range that was edited. What you're doing now is looking only at the "active cell", which doesn't leverage the benefits of the event object, and can also lead to bugginess when you make rapid changes.
Using the event object, when you make an edit to multiple cells at once (from copy/paste), you can then loop through the range and set your validations.
function onEdit(e) {
var editedRange = e.range;
var ss = editedRange.getSheet();
var tabValidation = "2018";
if(editedRange.getColumn() == 6 && editedRange.getRow() > 1 && ss.getSheetName() == tabValidation) {
var tabLists = "Leagues";
var tabListsSheet = e.source.getSheetByName(tabLists);
var makes = tabListsSheet.getRange(1, 1, 1, tabListsSheet.getLastColumn()).getValues(); // This won't change during execution, so call only once
var activeCell = editedRange.getCell(1,1); // Start with the first cell
var remainingRows = editedRange.getHeight();
while(remainingRows > 0) {
var cellValue = activeCell.getValue();
activeCell.offset(0, 1).clearContent().clearDataValidations(); // Always clear content & validations
if (cellValue != "") { // Add validations if cell isn't blank
var makeIndex = makes[0].indexOf(cellValue) + 1;
if(makeIndex != 0) {
var validationRange = tabListsSheet.getRange(3, makeIndex, tabListsSheet.getLastRow()-2);
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0, 1).setDataValidation(validationRule);
}
}
activeCell = activeCell.offset(1, 0); // Get the next cell down
remainingRows--; // Decrement the counter
}
}
}
I am searching for a way to sort my spreadsheet. I have looked around and found the "on edit" function but I am not entirely sure on how to use it. And how does it work with the runtime of only 5 minutes for google scripts, does a cellchange act as a trigger?
I found the following code:
function AutoSortOnEdit() {
var sheetNames = ["testsheet456", "testsheet457", "testsheet458"];
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheetNames.forEach(function(name) {
var sheet = ss.getSheetByName(name);
var range = sheet.getRange(5, 1, sheet.getLastRow() - 1, sheet.getLastColumn());
range.sort({column: 1, ascending: true});
});
Personally i would use this slightly modified version:
function AutoSortOnEdit() {
var sheet = SpreadsheetApp.openById("...").getSheetByName("...");
var range = sheet.getRange(5, 1, sheet.getLastRow() - 1, sheet.getLastColumn());
range.sort({column: 1, ascending: true});
});
Now I want to sort the sheet in alphabetical order sorted by the first row.
My questions are:
Does "ascending" mean that it sorts in alphabetical order?
Where do empty cells land(they should be at the end obviously)?
Does it gets triggered on every change? Can i manipulate to only sort it when Column A gets edited?
Regards Jonny
Edit: In the end i went with my slighlty modified version since onEdit was after all not fitting in the situation.
Yes, ascending is alphabetical. This code looks for only a change in the sheet named Sheet1 and only column A. The function must be named OnEdit (not AutoSortOnEdit).
function onEdit()
{
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetName= sheet.getSheetName();
if(sheetName=="Sheet1"){ //sheet to run on edit
var editRange = sheet.getActiveRange();
var editRow = editRange.getRow();
var editCol = editRange.getColumn();
var lr = sheet.getLastRow()
var range = sheet.getRange("A1:A"+lr);//apply on edit to
var rangeRowStart = range.getRow();
var rangeRowEnd = rangeRowStart + range.getHeight()-1;
var rangeColStart = range.getColumn();
var rangeColEnd = rangeColStart + range.getWidth()-1;
if (editRow >= rangeRowStart && editRow <= rangeRowEnd
&& editCol >= rangeColStart && editCol <= rangeColEnd)
{
var range = sheet.getSheetByName(sheetName).getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn());//Assumes header row
range.sort({column: 1, ascending: true});
}}}
Simple event triggers are limited to a 30 second execution time.
onEdit() events must be named simply onEdit() or onEdit(event)
onEdit can take a variable that represents the event that triggered it, that among other things, includes the range that was edited.
Sheet has it's own sort(). If you are trying to sort the entire sheet it is simpler to use that.
function onEdit(event){
if(event.range.getSheet().getName() == "Sheet1" && event.range.getColumn() == 1){//checks to see if the edited range was both on Sheet1 and column 1 (A)
event.range.getSheet().sort(1,true); //sort by first column
// event.range.getSheet().sort(1,false); //decending
}
}
multiple sheets can watched by expanding the conditionals, or aditional if statements.
I'm trying to create a drop down menu with contents based on a another cell in the same row. For example if A1 = 'yes' then the drop down in B2 gives you the options of 'yes' or 'no'. I can do this I have the list data set up and to code works. The problem is I need to do this 155 times in 4 different sheets. Is there a faster way to do this than right clicking and editing the data validation rules for each cell. Here's a link to the test sheet I'm working on :
https://docs.google.com/spreadsheets/d/1rd_Ig_wpof9R_L0IiA1aZ9syO7BWxb6jvBhPqG8Jmm4/edit?usp=sharing
You can set data validation rules with a script, as documented here. Here's a reference for starting with Apps scripts.
I wrote a function that does approximately what you described. It works with the range B3:B157 of the sheet '9th grade' in the current spreadsheet. For each of them, it sets the validation rule to be: a value in the same row, columns B and C of sheet 'List Data'. The line with
....... = listData.getRange(i+3, 2, 1, 2);
will need to be modified if the source range of validation is to be different. Here, the parameters are: starting row, starting column, number of rows, number of columns. So, 2 columns starting with the second, in row numbered i+3.
function setRules() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var grade = ss.getSheetByName('9th Grade');
var listData = ss.getSheetByName('List Data');
var range = grade.getRange('B3:B157');
var rules = range.getDataValidations();
for (var i = 0; i < rules.length; i++) {
var sourceRange = listData.getRange(i+3, 2, 1, 2);
rules[i][0] = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange).build();
}
range.setDataValidations(rules);
}
I land in this issue for a diferent reason: "Just mass DataValidation copy (or update) in one column". Thanks, to user3717023 that bring me a light.
I hope that helps someone this simplification.
function setRules() {
//select spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var leads = ss.getSheetByName('Leads');
//Select correct Datavalidation
var rangeNewDataValidation = leads.getRange('M2:M2');
var rule = rangeNewDataValidation.getDataValidations();
//Copy (or Update) Datavalidation in a specific (13 or 'M') column
var newRule = rule[0][0].copy();
Logger.log(leads.getMaxRows())
for( var i=3; i <= leads.getMaxRows(); i++){
var range = leads.getRange(i, 13);
range.setDataValidations([[newRule.build()]]);
}
}