I have a Google Spreadsheet with about 20 sheet to create reports using some importrange functions. I am trying to get this script to work by looking through all the sheets on edit and putting a border around any cell that has contents but it does not seems to be working.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var numSheets = ss.getNumSheets(); // count of sheets
// for every sheet
for( var s = 0; s < numSheets ; s++ ) {
var sheet = sheets[s]; // passes an individual sheet object to var sheet
//Add borders to non-empty
var range = SpreadsheetApp.getActiveSheet().getDataRange();
//set border
range.setBorder(false, false, false, false, false, false);
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] !== "") {
range.getCell(i + 1, j + 1).setBorder(true, true, true, true, true, true);
}
}
}
}
}
I would suggest trying this function instead:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getDataRange();
var maxCol = range.getLastColumn();
var maxRow = range.getLastRow();
for (var i=0; i<maxRow; i++)
{
for (var j=0; j<maxCol; j++)
{
if(!ss.getRange(i+1, j+1).isBlank()) //Check whether the cell is blank
{
ss.getRange(i+1, j+1).setBorder(true, true, true, true, true, true);
}
}
}
}
Reason being, the function you wrote runs everytime you make an edit to any of the sheets and iterates through all sheets. That is if you edit cell A14 on Sheet1, it will iterate through all 20 sheets checking the borders of the entire range for all sheets. Whereas, they might already have borders from it's previous run and nothing has changed in the other 19 sheets. This way, it iterates through the borders of the current sheet as soon as an edit is made on the sheet. However, I would like to bring 2 point for you to note with the current function:
onEdit only accepts user input as an "Edit".
Your sheet might end up looking something like this
Depending on what kind of data you're representing, this might or might not be what you desire. However, if I simplified the function a little more and used the following one:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getDataRange();
range.setBorder(true, true, true, true, true, true);
}
I get the following sheet, with a much simpler and faster code:
The first scenario does exactly what your description desires, by adding a border to every cell that has data. However, the second one will only add borders to the extent of your data table in respect of maximum row and column. Hope this helps!
Related
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;
}
}
}
}
}
I am trying to hide all columns in Google Sheets if a cell within that column contains the letter "S", I am checking row 6 which has the initials of each day of the week and want the ability to show and hide the weekends
Columns A6:G6 have M,T,W,T,F,S,S
Reason for the 9999 is due to this sheet containing multiple weeks and I am trying to look through all of them.
function Hide() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getDataRange();
var lastCol = data.getLastColumn()+1;
for(var i = 1; i < lastCol - 9999; i++) {
if(data.getCell(6, 9999 + i).getValue() === "S") {
sheet.hideColumns(9999 + i);
}
}
};
This is what I have so far but it doesn't seem to do anything, can anyone help me understand what I am missing please as I have been looking around and cannot find anything that has helped me with the issue?
Thank you!!
Try this. Google Apps scripts is basically Javascript. This is how I would loop through the columns since the used area of the sheet is dynamically changed, then it will stop at the last used column.
function Hide() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// get the entire 6th Row
var range = sheet.getRange("6:6");
// get number of columns to loop through
var num_cols = range.getNumColumns();
// loop through columns and check value one by one
// if value is equal to "S", then hide the column
for (var i = 1; i <= num_cols; i++) {
var value = sheet.getRange(6,i).getValue();
if (value == "S") {
sheet.hideColumns(i);
};
}
}
I am trying to create some basic conditional formatting rules with Scripts instead of the conditional formatting editor in sheets, because when a new row is added or removed, it breaks the rules already set. For example, when a row is deleted, the condition may be for the range A:C but then it adds "A1:C5,A6:C898". This causes some rows to be skipped by the rules so I am hoping a script will solve this.
So I want to simply change the cell background to green if the cell text is exactly "Y". I want to change it to red if text is exactly "N". I have other rules that I want to use but I am having trouble with the basics on this.
What I have so far in my script:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PRECLOSING");
var range = sheet.getRange("E:E,I:J,M:M,P:P,S:V,AB:AC,AF:AF");
range.activate();
var values = rangeY.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) {
var cell = sheet.getRange(i + 1, 2);
if ( values == "X" )
{
cell.setBackground('black');
return;
} else {
cell.setBackground('white');
}
}
}
I made some modifications to your code.
The function getRange i don't think it can accept the ranges in that way, instead i think it can get just ranges that are continue (eg. A1:E10)
For that I created an array with the ranges you need and then I loop through them.
The function "getValues" returns a two dimensional array so you will need a second loop to get the actual value from the cell.
Then as you already have the range, you need to get the cell inside that range and not a new complete range.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PRECLOSING");;
var columns = ['E:E','I:J','M:M','P:P','S:V','AB:AC','AF:AF'];
for(var col in columns){ //loop to iterate the desired ranges
var range = sheet.getRange(columns[col]);
range.activate();
var values = range.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) { //loop to iterate the rows
for( var j = 0; j< values[i].length ; j++){ //loop to iterate the columns
var cell = range.getCell(i+1, j+1);
if ( values[i][j] == "X" )
{
cell.setBackground('black');
}
}
}
}
}
Hope this helps.
I have a google doc that has formulas in column C and Column D. It's a forecasting sheet so I know what we have on hand (C) and what we have on order (D). I want a script that opens a menu at the top so I can quickly hide or unhide Column C values that are equal to 0 and the same with column D.
I've pulled this script from someone on here to create a menu for hiding rows but I can't get it to work with my application. I'm not sure if the formulas in the cells prevent the function from finding 0's or not.
UPDATE: I have the first function working. It hides all rows with a 0 in column C. When a row is hidden and is updated to have a value, the function does not unhide it though. The second function is still not unhiding all. Once I get these two figured out I can build out the same thing for Column D. Below is updated script
function onOpen() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// create menu
var menu = [{name: "Show Only On Hand", functionName: "hideRow"},
{name: "Show All", functionName: "showRow"}];
// add to menu
ss.addMenu("Filter", menu);
}
function hideRow() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheetByName('Inventory')
// get data
var data = sheet.getDataRange();
// get number of rows
var lastRow = data.getLastRow()+1;
Logger.log(lastRow);
// itterate through rows
for(var i=1; i<lastRow; i++) {
if(data.getCell(i, 3).getValue() == 0) {
sheet.hideRows(i);
}
}
}
function showRow() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheets()[0];
// get data
var data = sheet.getDataRange();
// get number of rows
var lastRow = data.getLastRow();
// show all rows
sheet.showRows(lastRow, 1);
}
I won't do everything for you, cause this isn't this sites goal, but I'll point to where the script could be failing, by line:
var sheet = ss.getSheets()[0];
Is it always the first sheet you're using? If not, you can use the method getSheetByName().
for(var i=1; i<lastCol; i++) {
This iterates trough the columns, do you really want that? If it is for rows, you'll need to change the getLastColumn for a row method.
if(data.getCell(i, 3).getValue() == '0') {
Again, if it is row that you want to hide, you'll have to refactor getCell, since i is the row, and 3 is the column, you can add another conditional since you also want column D.
Also, you may need to log the data.getCell() to check if the 0 is really coming as a String, if it is coming as a number remove the single quotes.
sheet.hideColumns(i);
Again, for row, this need to be refactored.
Update:
To show all you can:
var sheet = SpreadsheetApp.getActiveSheet();
var fullSheetRange = sheet.getRange(1,1,sheet.getMaxRows(), sheet.getMaxColumns() )
sheet.unhideColumn( fullSheetRange );
sheet.unhideRow( fullSheetRange ) ;
And then hide them. To do this for column all you have to do is copy and change the column C specied in getCell(), you can add another condition in the if with the OR logical operator.
Huge thanks to Kriggs for helping me figure this one out. I'm sure it's a bit clunky, but it works perfect for what I need.
function onOpen() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// create menu
var menu = [{name: "Show Only On Hand", functionName: "hideRow"},
{name: "Show Only On Order", functionName: "hideorder"}];
// add to menu
ss.addMenu("Filter", menu);
}
function hideRow() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheetByName('Inventory')
// get data
var data = sheet.getDataRange();
// get number of rows
var lastRow = data.getLastRow()+1;
Logger.log(lastRow);
var sheet = SpreadsheetApp.getActiveSheet();
var fullSheetRange = sheet.getRange(1,1,sheet.getMaxRows(), sheet.getMaxColumns() )
sheet.unhideColumn( fullSheetRange );
sheet.unhideRow( fullSheetRange ) ;
// itterate through rows
for(var i=1; i<lastRow; i++) {
if(data.getCell(i, 3).getValue() == 0) {
sheet.hideRows(i);
}
}
}
function hideorder() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheetByName('Inventory');
// get data
var data = sheet.getDataRange();
// get number of rows
var lastRow = data.getLastRow()+1;
Logger.log(lastRow);
var sheet = SpreadsheetApp.getActiveSheet();
var fullSheetRange = sheet.getRange(1,1,sheet.getMaxRows(), sheet.getMaxColumns() )
sheet.unhideColumn( fullSheetRange );
sheet.unhideRow( fullSheetRange ) ;
// itterate through rows
for(var i=1; i<lastRow; i++) {
if(data.getCell(i, 4).getValue() == 0) {
sheet.hideRows(i);
}
}
}
In Google Sheets I have a sheet with Form Responses, and to the right of the form columns I have columns with formulas that use form data for functions.
At the start I had the formulas extended down the rows so they worked on new form submissions, but found that new form submissions would clear the row out :(.
Instead of manually extending formulas down after each submission I installed Andrew Stillman's copyDown() script; what it does is copies down the formulas after scripts are submitted.
Now the problem I'm having is that the script works when run manually, but when I set to trigger on form submits it copies the said formula on that sheet and on all other sheets in the spreadsheet. I Do Not Want that side-effect, as it messes the whole spreadsheet up. :((
What I thought to do is edit script so it only works on the one Form Response sheet, not all sheets. But I don't know how to do that.
The name of the sheet I want it to run on is "Requests", and the gid=8.
How do I edit this script to only work for that one sheet?
To get code to run on just a particular sheet use the .getSheetByName() method. For example:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var reqSh = ss.getSheetByName('Requests');
There is another way that might be easier. You could try keeping one sheet purely for form submissions and use an arrayformula in a second sheet to copy any values from the first sheet to the same range in the second.
=ARRAYFORMULA('Requests'!A1:H) would copy columns A to H.
I had the same problem as you and this was the solution. I placed my formulas in the second sheet in the columns to the right of the range and copied them down in the normal way. The formulas referred to the copied range in the second sheet. It worked a treat.
I didn't come up with the idea myself - I'm sure it was suggested by someone on the Google spreadsheet forum. I should give you a link to the post but I just looked and I can't find it.
In your code you have (comments in code)
var sheets = ss.getSheets() [8]; // you choose sheet [8]
var cellAddresses = new Object();
for (var i=0; i<sheets.length; i++) { // but you enter a for loop that adresses every sheet in turn...
var range = sheets[i].getDataRange();
You should simply suppress this loop and use only the sheet number you want to be proceeded ...
The simplest way could be to do it like this :
var i = 8
var sheets = ss.getSheets() [i];
var cellAddresses = new Object();
var range = sheets[i].getDataRange();
...
and at the end of the loop delete the } that fitted the for loop
EDIT : the new code should be like this :
function copydown() {
setCopyDownUid();
setCopyDownSid();
logCopyDown();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets() [8];
var cellAddresses = new Object();
var i=8
// for (var i=0; i<sheets.length; i++) {
var range = sheets[i].getDataRange();
var lastRow = range.getLastRow();
var values = range.getValues();
for (var j=0; j<values.length; j++) {
for (var k=0; k<values[j].length; k++) {
var test = values[j][k].toString();
var start = test.indexOf("copydown");
if (start == 0) {
start = start+10;
var end = test.length-2;
var length = end-start;
var value = test.substr(start, length);
var col = k+1;
var nextRow = j+2;
var numRows = lastRow-(nextRow-1);
if (numRows>0) {
var destRange = sheets[i].getRange(nextRow, col, numRows, 1);
destRange.clear();
var newLastRow = sheets[i].getDataRange().getLastRow();
var newNumRows = newLastRow-(nextRow-1);
var newDestRange = sheets[i].getRange(nextRow, col, newNumRows, 1);
var cell = sheets[i].getRange(nextRow-1, col);
cell.setFormula(value);
cell.copyTo(newDestRange);
}
var cellAddress = cell.getA1Notation();
cellAddresses[cellAddress] = test;
}
}
}
Utilities.sleep(500);
resetCellValues(cellAddresses, sheets[i]);
}
//}