I'm essentially trying to do exactly what was done in this question, but with columns instead of rows. When I run the script there as is, it works fine. But just switching all references to columns to rows (and vice versa) isn't working for me, for some reason, and I can't figure out what's wrong.
For reference, this is what I have:
function onOpen()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('My Tools')
.addItem('Hide Columns','hideColumnsDate')
.addToUi();
}
function hideColumnsDate(row)
{
var row = (typeof(row) !== 'undefined') ? row : '1';
var day = 86400000;
var today = new Date().getTime();
var rng = SpreadsheetApp.getActiveSheet().getRange(row + ':' + row);
var rngA = rng.getValues();
for(var i = 0; i < rngA.length ;i++)
{
if(isDate(rngA[i][0]) && (((today - new Date(rngA[i][0]).getTime())/day) > 7 ))
{
SpreadsheetApp.getActiveSheet().hideColumns(i + 1);
}
}
}
function isDate (x)
{
return (null != x) && !isNaN(x) && ("undefined" !== typeof x.getDate);
}
When your script is modified, how about this modification? Please think of this as just one of several modifications.
Modification points:
In your situation, the values retrieved from the range of SpreadsheetApp.getActiveSheet().getRange(row + ':' + row) is [[column1, column2, column3,,,]].
In this case, the length of the for loop is rngA[0].length.
In order to retrieve the values of the columns, please modify rngA[i][0] to rngA[0][i].
Modified script:
From:
for(var i = 0; i < rngA.length ;i++)
{
if(isDate(rngA[i][0]) && (((today - new Date(rngA[i][0]).getTime())/day) > 7 ))
To:
for(var i = 0; i < rngA[0].length ;i++)
{
if(isDate(rngA[0][i]) && (((today - new Date(rngA[0][i]).getTime())/day) > 7 ))
Note:
In your case, as other pattern, you can also use the following modification.
Modify var rngA = rng.getValues(); to var rngA = rng.getValues()[0];, and modify rngA[i][0] to rngA[i].
In above modified script, all rows of a column are checked. If you want to check the specific columns, please tell me.
Reference:
getValues()
If this was not the result you want, I apologize.
function hideColumnsDate(row) {
var row=row||1;
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getRange(row,1,1,sh.getLastColumn());
var vA=rg.getValues()[0];
var today=new Date().valueOf();
vA.forEach(function(e,i){
var rowdate=new Date(e).valueOf();
if(((today-rowdate)/86400000)>7) {
sh.hideColumns(i+1);
}
});
}
Related
Im trying to detect:
If it is a date
If it is before todays date (Regardless of the hour)
If point 1 and point 2 are ok, than mark in red.
Can somebody help me please? Here is my google sheet: https://docs.google.com/spreadsheets/d/1EPW4qbv1K55risE9hpiy5rdYWtmufJb4T3o-3Dzhp-g/edit?usp=sharing
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
for (var i=2; i<=8; i++){
var data = ss.getRange(i,1).getValue();
var isDate = data instanceof Date;
var today = now.getTime().getValue();
if(data = isDate && data < today){
ss.getRange(i,1).setBackground("red");
}
}
}
Try it this way:
function myFunction() {
var ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const vs = sh.getRange(2, 1, 7).getValues().flat();
const bs = sh.getRange(2, 1, 7).getBackgrounds();
const dt = new Date();
const td = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
const tdv = td.valueOf();
vs.forEach((e, i) => {
if (Object.prototype.toString.call(e) === '[object Date]' && e.valueOf() < tdv) {
bs[i][0] = '#ff0000';
}
})
sh.getRange(2,1,bs.length,1).setBackgrounds(bs);
}
I believe your goal is as follows.
In the column "A" of the Spreadsheet, when the value is the date object and the date is before today, you want to set the background color of the cell to "red" using Google Apps Script.
In this case, how about the following sample script?
Sample script:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getRange("A2:A" + sheet.getLastRow());
var today = new Date().getTime();
var colors = range.getValues().map(([a]) => [a instanceof Date && a.getTime() < today ? "red" : null]);
range.setBackgrounds(colors);
}
When this script is used, I thought that your goal might be able to be achieved.
In your script, getValue and setBackground are used in a loop. In this case, the process cost will become high. Ref So, I proposed the above sample script.
In the above script, the background color of cells is overwritten. For example, if you want to set the background color to only the searched cells, how about the following sample script?
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var today = new Date().getTime();
var ranges = sheet.getRange("A2:A" + sheet.getLastRow()).getValues().flatMap(([a], i) => a instanceof Date && a.getTime() < today ? [`A${i + 2}`] : []);
sheet.getRangeList(ranges).setBackground("red");
}
References:
getValues()
map()
setBackgrounds(color)
Added:
From your following reply,
but that code you offer is a bit advanced for me.
How about the following modified script?
Sample script:
function myFunction2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getRange("A2:A" + sheet.getLastRow());
var values = range.getValues();
var today = new Date().getTime();
var ranges = [];
for (var i = 0; i < values.length; i++) {
if (values[i][0] instanceof Date && values[i][0].getTime() < today) {
ranges.push(`A${i + 2}`);
}
}
sheet.getRangeList(ranges).setBackground("red");
}
function getNamedRange( e ) {
const sheet = SpreadsheetApp.getActiveSheet();
const activeRange = e.range;
const namedRange = sheet.getNamedRange( activeRange.getA1Notation() );
//alert named range
SpreadsheetApp.getUi().alert( namedRange );
return namedRange;
}
I have tried multiple ways of doing this to no avail.
Main goal: get the name of the named range where the edited cell is found
So if A1:C5 was a named range of "firstRange"
and I edited cell A2, the onEdit(e) would run getNamedRange(e) and alert "firstRange"
I have tried getName() and all sorts of combos using the reference section
Google Reference Link
First, when I saw this question and Programmatically test if a Google Sheet cell is in a named range, also I thought that this might be the same situation. But, I noticed that in my answer, the intersection ranges between the specific range and the specific named range are retrieved. I thought that the basic method is the same. So in order to use my answer for this question, it is required to modify a little. So in this answer, I would like to propose the sample script for achieving the goal by modifying it.
Sample script 1:
When this sample script is modified for your script, it becomes as follows.
function getNamedRange(e) {
var inputRange = e.range;
var columnToLetter = function (column) { // <--- https://stackoverflow.com/a/21231012/7108653
var temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
};
var res = [];
var result = [];
var sheet = SpreadsheetApp.getActiveSheet();
var namedRanges = sheet.getNamedRanges();
for (var i = 0; i < namedRanges.length; i++) {
var nr = namedRanges[i];
// Retrieve a1Notations from "inputRange".
var iStartRow = inputRange.getRow();
var iEndRow = iStartRow + inputRange.getNumRows() - 1;
var iStartColumn = inputRange.getColumn();
var iEndColumn = iStartColumn + inputRange.getNumColumns() - 1;
var irA1Notations = [];
for (var j = iStartRow; j <= iEndRow; j++) {
var temp = [];
for (var k = iStartColumn; k <= iEndColumn; k++) {
temp.push(columnToLetter(k) + j);
}
Array.prototype.push.apply(irA1Notations, temp);
}
// Retrieve a1Notations from "myNamedRange".
var namedRange = nr.getRange();
var nStartRow = namedRange.getRow();
var nEndRow = nStartRow + namedRange.getNumRows() - 1;
var nStartColumn = namedRange.getColumn();
var nEndColumn = nStartColumn + namedRange.getNumColumns() - 1;
var nrA1Notations = {};
for (var j = nStartRow; j <= nEndRow; j++) {
for (var k = nStartColumn; k <= nEndColumn; k++) {
nrA1Notations[columnToLetter(k) + j] = null;
}
}
// Retrieve intersection ranges.
result = irA1Notations.filter(function (e) { return nrA1Notations.hasOwnProperty(e) });
if (result.length > 0) {
res.push(nr.getName())
}
}
if (res.length == 0) return;
SpreadsheetApp.getUi().alert(res.join(","));
}
Sample script 2:
In this case, I thought that the following simple script might be able to be used.
function getNamedRange(e) {
const range = e.range;
const sheet = SpreadsheetApp.getActiveSheet();
const r = sheet.getNamedRanges().filter(r => {
const temp = r.getRange();
const startRow = temp.getRow();
const endRow = startRow + temp.getNumRows();
const startCol = temp.getColumn();
const endCol = startCol + temp.getNumColumns();
return (range.rowStart >= startRow && range.rowStart <= endRow && range.columnStart >= startCol && range.columnStart <= endCol) ? true : false;
});
if (r.length == 0) return;
SpreadsheetApp.getUi().alert(r.map(f => f.getName()).join(","));
}
Note:
When you edit a cell, when the edited cell is included in the named range, a dialog is opened. And, you can see the name of the named range.
From this question, it seems that you are using getNamedRange as the installable OnEdit trigger. In the above scripts, you can also use the simple trigger. So you can also modify the function name from getNamedRange to onEdit.
In your script, return namedRange; is used. But when getNamedRange is run using the installable OnEdit trigger, I thought that return namedRange; is not used.
Reference:
Related thread.
Programmatically test if a Google Sheet cell is in a named range
I am planning to create a cell where if the cells in G2 is empty then it will copy the content from row A but if the cell in column G is updated with something else I dont want to copy the content from row A. I have done code something like this in App Script but it is not working.
function getLast(range) {
var getResult = function(range) {
if (!((range.getNumRows() > 1 && range.getNumColumns() == 1) || (range.getNumRows() == 1 && range.getNumColumns() > 1))) {
throw new Error("Please input one row or one column.");
}
var v = Array.prototype.concat.apply([], range.getValues());
var f = Array.prototype.concat.apply([], range.getFormulas());
var i;
for (i = v.length - 1; i >= 0; i--) {
if (v[i] != "" || f[i] != "") break;
}
return i + 1;
};
if (Array.isArray(range)) {
return range.map(function(e) {
return getResult(e);
});
} else {
try {
range.getA1Notation();
} catch (e) {
throw new Error("Inputted value is not a range.");
}
return getResult(range);
}
}
function formulasheets(){
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName("USERNAMES");
sheet.getRange("G2").setFormula('=IF(G2="",A2, ');
var range1 = sheet.getRange("B:B");
var fillDownRangecolumnI = sheet.getRange(2, 7, lr-1);
sheet.getRange("G2").copyTo(fillDownRangecolumnA);
}
I have attached the image of my google sheet to explain better on what I want. Can ayone help me on this. Thank you.
If Cell G2 & G5 is empty
It should be updated with content in row A
I see your question, and I would like to give a try, hope it is useful for you. Please refer to my edited code below, I think it is better for you to use for loop to check for blank, and to avoid write formula:
function formulasheets(){
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName("USERNAMES");
for (var x=1;x <= sheet.getLastRow();x++){
var cell = sheet.getRange(x,7);
if (cell.isBlank()){
sheet.getRange(x,1).copyTo(cell);
}
}
}
Before execution
After execution, accept if help, it is my first time answer G Apps Script :)
Can anyone help me do this?
My problem is that all of my three (3) codes has the same name "onEdit".
How can I run three (3) of of them?
There is a suggestion that I need to change their name, but when I change the names my codes wont run.
Can anyone post an example code for me, for my reference?
Please?
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
for (p=1 ; p<=lastRow ; p++) { // p <= lastRow
var status = sheet.getRange("C"+p).getValue(); // Change P to the completed column
if (status == "no") { // status == "no"
sheet.hideRows(p);
}
}
}
function onEdit() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var cell = spreadsheet.getActiveCell();
var col = cell.getColumn();
var row = cell.getRow();
var rows = [1, 2, 3];
// This is a list of the rows that should blink if edited.
if (col === 7 && rows.indexOf(row) !== -7 && sheet.getName() === 'Sheet1') {
// If the edited cell is in column A (1) and if the edited cell
// is one of the rows listed
for (var num = 0; num < 50; num++) {
var colour = num%2 === 0
? 'GOLD'
: 'WHITE';
// Using ? and : like this is called a ternary operation. It's a
// shorter form of if. ifStatement ? true : false.
sheet.getRange('G' + row + ':G' + row).setBackground(colour);
// Get the range for the edited row and set the bg colour
SpreadsheetApp.flush();
Utilities.sleep(500);
}
}
}
Two functions in one onEdit()
Here's an example that combines two functions into one onEdit() simple trigger.
function onEdit(e){
var ss=e.source;
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
Logger.log('Name: %s',name);
var includedSheets=['Sheet45','Sheet46']
if(includedSheets.indexOf(name)==-1){
return;
}
if(name=='Sheet45'){
Sheet45(e);//You could put the code right here but I wanted to make it clear that it's two different operation. This one deletes a row if all conditions are met.
}
if(name=='Sheet46'){
Sheet46(e);//This one deletes a column if all conditions are met
}
}
function Sheet45(e){//You can name them whatever you want
var sh=e.range.getSheet();
var row=e.range.getRow();
var col=e.range.getColumn();
Logger.log('Name: %s',e.range.getSheet().getName());
if(col==1 && row==4 && e.value=='delete'){//if column1 and row4 is changed to 'delete'
sh.deleteRow(row);//the it deletes row 4
}
}
function Sheet46(e){
var sh=e.range.getSheet();
var row=e.range.getRow();
var col=e.range.getColumn();
Logger.log('Name: %s',e.range.getSheet().getName());
if(row==1 && col==4 && e.value=='delete'){//if column4 and row1 is changed to 'delete'
var rg=sh.getDataRange();//then it deletes column 4
var vA=rg.getValues();
for(var i=0;i<vA.length;i++){
vA[i].splice(3,1);
Logger.log('vA[%s]: %s',i,vA[i]);
}
}
rg.clear();
sh.getRange(1,1,vA.length,vA[0].length).setValues(vA);
}
There are a lot of different ways to do it. I like to return as quickly as possible on sheets that are not involved in any of the functions.
Simple Triggers
Spreadsheet Documentation
You probably already know that you can't run these onEdit() functions directly unless you provide the event object.
Your two functions combined
I modified the second one a bit because it didn't make sense to me. So you change it since you probably understand what you want.
function onEdit(e){
oE1(e);
oE2(e);
}
function oE1(e) {//this works on all sheet
var ss=e.source;
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
var lastRow = sh.getLastRow();
for(var row=1;row<=lastRow;row++) {
var status=sh.getRange(row,3).getValue();//Column3 is C
if (typeof(status)=="string" && status.toLowerCase()=="no") {
sh.hideRows(row);
}
}
}
function oE2(e) {//this only works on sheet 1
var ss=e.source;
var rg=e.range
var sh=rg.getSheet();
var row=rg.getRow();
var col=rg.getColumn();
var cell=sh.getRange(row,col);
var rows = [1, 2, 3];
if(col==1 && rows.indexOf(row)!=-1 && sh.getName()=='Sheet1') {
sh.getRange(row,7).setBackground((rows.indexOf(row)%2==0)?'Gold':'White');
}
}
It would be nice to rework the organization so that you can return more quickly for sheets that are not involved with either function. It's really helpful to look at the executions page when debugging these functions.
I am trying to calculate some values using my own function (showing dummy function in this question), and I'd like the function to trigger when the user edits a cell in a given range, however even after writing the trigger the only time when the value is calculated again is when I go to the script editor and click Save.
My function:
function calculate(column) {
var sheet = SpreadsheetApp.getActiveSheet();
var column = sheet.getActiveCell().getColumn();
var values2d = sheet.getRange(1, 1, 15).getValues();
var values = [].concat.apply([], values2d);
var testRange2d = sheet.getRange(1, column, 15).getValues();
var testRange = [].concat.apply([], testRange2d);
var sum = 0;
for (var i = 0; i < 15; i++) {
if (testRange[i] == true) sum += values[i]
}
return sum;
}
function onEdit(e) {
var activeCell = SpreadsheetApp.getActiveSheet().getActiveCell();
if (activeCell.getBackground() === "#e3f5a2") activeCell.setBackground("#ffffff");
else activeCell.setBackground("#e3f5a2");
calculate();
}
And the sheet:
The cell besides total is where I call my function. And the value will only be updated either manually or as described earlier, but even when I trigger an event, I have also tried doing other actions in the trigger like changing a random cells value and that worked however.
Might i suggest the following. This will only occur if a checkbox in column 2 is changed.
function onEdit(event) {
try {
var sheet = event.source.getSheetByName("Sheet1")
if( event.range.getSheet().getName() === "Sheet1" ) {
if( event.range.getColumn() === 2 ) {
var values = sheet.getRange(1,1,15,2).getValues();
var sum = 0;
for( var i=0; i<values.length; i++ ) {
if( values[i][1] ) sum += values[i][0];
}
sheet.getRange(16,2,1,1).setValue(sum);
if( event.range.getBackground() === "#e3f5a2" )
event.range.setBackground("#ffffff");
else
event.range.setBackground("#e3f5a2");
}
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}