Essentially, I have a few sheets with the following setup:
A few frozen title rows at the top of the sheet, underneath that many rows of data all with a date in column A. The following function allows me to jump to 'today' on any sheet (well, technically the closest day in the future if there isn't data for today).
function goToToday() {
var workbook = SpreadsheetApp.getActiveSpreadsheet()
var currentSheet = workbook.getActiveSheet().getName();
if (currentSheet !== "Calculations") {
workbook.getSheetByName(currentSheet).setActiveRange(workbook.getSheetByName(currentSheet).getRange(1,1))
var today = new Date()
var rowNumber = 4
var numOfRows = workbook.getSheetByName(currentSheet).getLastRow() - 3
var dates = workbook.getSheetByName(currentSheet).getRange(4,1,numOfRows,1).getValues()
for(i = 0; i < numOfRows; i++) {
if(dates[i][0] > today) {
break
}
else {
rowNumber++
}
}
workbook.getSheetByName(currentSheet).getRange(rowNumber, 1).activate()
}
}
What's bugging me is if active cell prior to running the function is below the returned 'today' cell of the function, the cell is returned as the top left cell in the window, which is perfect. If however the current active cell is above the returned cell, the cell is returned near the bottom of the window. How can I make the function return the cell consistently as the top left cell of the window? I assume this has something to do with scrolling..
function goToToday() {
var workbook = SpreadsheetApp.getActiveSpreadsheet(),
currentSheet = workbook.getActiveSheet();
if ( currentSheet.getName() !== "Calculations" ) {
var lastCell = currentSheet.getRange(
currentSheet.getMaxRows(),getMaxColumns()
);
currentSheet.setActiveRange("A1");
var today = new Date(),
rowNumber = 4,
numOfRows = currentSheet.getLastRow() - 3,
dates = currentSheet.getRange(4,1,numOfRows,1).getValues();
for(i = 0; i < numOfRows; i++) {
if(dates[i][0] > today) {
break
}
else {
rowNumber++
}
}
currentSheet.setActiveRange(lastcell);
SpreadsheetApp.flush();
currentSheet.getRange(rowNumber, 1).activate()
}
}
I refactored some function calls, but the main idea is to set the active range to be the bottom right cell, flush the sheet (which should update the UI) and then set the active range to the target.
EDIT: In refactoring the OP's code, I moved the call to .getName() into the conditional, and just stored the sheet in currentSheet. This eliminated multiple calls to worksheet.getSheetByName(currentSheet). Not a really big deal here, but in a larger script, run time would suffer. And, IMO, it's easier to read.
I don't think they are methods in Google Apps Script that can control the scroll, you can request a new feature.
A workaround (not the best one) is to use hideRows() and inmediately call showRows() that will make the UI "scroll". An example assuming the first row is frozen:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
sheet.hideRows(2, 199);
sheet.showRows(2, 199);
sheet.getRange(200, 2).activate();
}
Adapting HardScale's solution, I got this to work. I'ts not the most well written and I'm sure there are plenty of ways to 'thin in out' so to speak, but it works!
function goToToday() {
var workbook = SpreadsheetApp.getActiveSpreadsheet()
var currentSheet = workbook.getActiveSheet().getName();
if (currentSheet !== "Calculations") {
var lastCell = workbook.getSheetByName(currentSheet).getRange(workbook.getSheetByName(currentSheet).getMaxRows(),workbook.getSheetByName(currentSheet).getMaxColumns());
workbook.getSheetByName(currentSheet).setActiveRange(workbook.getSheetByName(currentSheet).getRange(1,1))
var today = new Date()
var rowNumber = 4
var numOfRows = workbook.getSheetByName(currentSheet).getLastRow() - 3
var dates = workbook.getSheetByName(currentSheet).getRange(4,1,numOfRows,1).getValues()
for(i = 0; i < numOfRows; i++) {
if(dates[i][0] > today) {
break
}
else {
rowNumber++
}
}
workbook.getSheetByName(currentSheet).setActiveRange(lastCell);
SpreadsheetApp.flush();
workbook.getSheetByName(currentSheet).getRange(rowNumber, 1).activate()
}
}
Related
I have a range of week numbers and their corresponding dates in a column J. Week 1: 1/2 - 1/8, and so on. This is populated by:
=ArrayFormula(
LAMBDA(FIRSTOFYEAR,
LAMBDA(DATES,
LAMBDA(WEEKS,
LAMBDA(DATA,
BYROW(ARRAY_CONSTRAIN(DATA,MAX(WEEKS),4),LAMBDA(ROW,JOIN(" ",ROW)))
)(TO_TEXT(QUERY(QUERY({WEEKS,"Week "&WEEKS&":",DATES,DATES},"SELECT Col2,MIN(Col3),'~',MAX(Col4),Col1 GROUP BY Col1,Col2",0),"OFFSET 1 FORMAT Col2'm/d',Col4'm/d'",0)))
)(WEEKNUM(DATES))
)(BYROW(SEQUENCE(365,1,0),LAMBDA(NUM,FIRSTOFYEAR+NUM)))
)(DATE(2023,1,1))
)
I also have a script to get the number of the current sheet:
function getSheetNum() {
const sss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (const [index,sheet] of sheets.entries()) {
if(sheet.getName() === sss.getActiveSheet().getName()) return index;
}
}
What I am trying to do, and not doing correctly yet, is to have a script that names the sheet based on the current week. So, the first sheet is sheet 1 and thus will be named Week 1: 1/2 - 1/8. The second sheet is sheet 2 and so it'll be named Week 2: 1/9 - 1/15, and so on. My current, non-working script is below:
function nameSheet() {
var sheet = SpreadsheetApp.getActiveSheet();
var week = getSheetNum();
var oldName = sheet.getName();
var newName = week.getValue();
if (newName.toString().length>0 && newName !== oldName) {
sheet.setName(newName);
}
}
What am I doing wrong??
It will make unnecessary complexity into a function if you have to shift a day from original day and week counts in common sense... which is very much not recommended. And at the same time it may cause unexpected issues when working together with other functions.
but anyway, the following script will create a list of sheet names in the format you want, and set the sheet names with those generated names in ascending order.
class getDate{} is a object class to help modify the Date() object.
createSheetNames(year) is a function which accept an input as to indicate which year of sheet names are you working with, this function will look for the first Monday of the given year to begin with, and return an array of results with a sheet name for each week.
setSheets() is a function which will iterate all sheets you have in your working spreadsheet, and rename each sheet according to the sheet names returned by the last function.
class getDate {
constructor (input) {
if(!!input) this.date = new Date(input);
else this.date = new Date();
}
get year() { return this.date.getFullYear(); }
get month() { return this.date.getMonth() + 1; }
get week() { return this.date.getDay(); }
get day() { return this.date.getDate(); }
calDay(num) {
if(isNaN(num)) throw new Error('requires integer.');
return new getDate(this.date.setDate(this.day + Math.floor(num)));
}
}
function createSheetNames(year) {
const results = [];
const date = new getDate(`${year}-01-01`);
const isInt = (num) => num === Math.floor(num);
while (date.week !== 1) date.calDay(+1);
for (i=0;i<365+7;i++) {
if (new getDate(date.date).calDay(-6).year > year) break; // change -6 of this line to 0 will remove the last week of a year if that week ends inside the next year.
const calWeek = (i + 1)/7;
if (isInt(calWeek)) {
const sheetName = `Week ${calWeek}: ${date.calDay(-6).month}/${date.day} ~ ${date.calDay(+6).month}/${date.day}`;
results.push(sheetName);
}
date.calDay(+1)
}
return results;
}
function setSheets() {
const sheetNames = createSheetNames(2023); // enter the year of sheet name you need here.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = ss.getSheets();
for (const [i,sheet] of sheets.entries()) {
if (i > sheetNames.length - 1) break;
sheet.setName(sheetNames[i]);
}
}
After putting these codes into your spreadsheet, run the setSheets() function for one time, it will replace all your sheet names.
2023-01-11 update:
Fixed a couple of typos that may lead to unexpected results, and removed some variables that are not necessary in this using case which may? enchance execution speed very slightly.
You can try this code, If you will start renaming it from Sheet 1 forward although you should set the Activesheet() on the First sheet.:
function renameSheets() {
const ss = SpreadsheetApp.getActive();
var sheets = ss.getSheets();
const weekNames = ss.getActiveSheet().getRange("J1:J").getDisplayValues().flat();
for (i=0; i<sheets.length; i++) {
if(sheets[i].getName() != weekNames[i]){
sheets[i].setName(weekNames[i]);
}
}
}
Result:
Alternatively, you can use this code if you want to have a main/fixed sheet which you will not rename:
function renameSheets2() {
const ss = SpreadsheetApp.getActive();
var sheets = ss.getSheets();
const weekNames = ss.getRange("MAIN!J1:J").getDisplayValues().flat();
for (i=1; i<sheets.length; i++) {
if(sheets[i].getName() != weekNames[i-1]){
sheets[i].setName(weekNames[i-1]);
}
}
}
Result:
For example, I have named my First sheet as Main.
Since you already have the desired sheet names in a spreadsheet range, try renaming sheets to those names, like this:
function nameSheetsByWeek() {
const ss = SpreadsheetApp.getActive();
const weekNames = ss.getRange('Sheet1!A2:A').getDisplayValues().flat();
ss.getSheets().forEach((sheet, index) => {
const newSheetName = weekNames[index - 1];
if (!index || !newSheetName) {
return;
}
sheet.setName(newSheetName);
});
}
I'm using Google Sheets function for analyze some data, but even if i have not huge database, the sheet is lagging with my function. The function is:
=ARRAY_CONSTRAIN(ARRAYFORMULA(SUMIF(IF(A2:A10000="Received",ROW(A2:A10000),""), "<="&ROW(A2:A10000), B2:B10000)+G1-SUMIF(IF(A2:A10000="Given",ROW(A2:A10000),""), "<="&ROW(A2:A10000), B2:B10000)),COUNTA(B2:B10000),1)
Is it possible to use this function via Google script so as not to overload the sheet?
Example sheet: https://docs.google.com/spreadsheets/d/1UeIXFVsP5hevC20D04juTstBbfViYhWUIp6VRst_Nu4
Try this script. It worked correctly in my copy.
The purpose is to take previous value and add or subtract new value depending on the condition in column A
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var previous = s.getRange('G1').getValue(); //ger vaule from last month
var valuecount = s.getLastRow() ; // defines number of rows
for (let i = 1; i < valuecount; i++) { //starts loop
var direction = s.getRange(i+1 , 1).getValue();
if (direction == 'Received') {
previous = previous + s.getRange(i+1 , 2).getValue() ;
}
else
{previous = previous - s.getRange(i+1 , 2).getValue() }
s.getRange(2,4,valuecount).getCell(i,1).setValue(previous);
}
}
I have a script which combines three scripts to do the following:
1) Insert rows from one tab to the top of another tab
2) Remove duplicates from the tab in which the data was just added
3) Clear out the old tab from which the data was just ported over from
For the De-dupe script, it deletes rows starting at the bottom and then goes up. So I'm having established and existing data deleted. What I need it to do is start at the top and go down. So if new row records ported over from the first script are found to be a duplicate, it should delete those instead.
How can I get the de-dupe script to essentially process the opposite way?
I did find reverse logic with the below link, but I can't find a way to make it work with my script and keep getting errors. I'm also not sure if this would be the best methodology to fit in with my overall script.
Link: Removing Duplicate Rows in a google Spreadsheet from the end row
function Run(){
insert();
removeDuplicates();
clear1();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
//Code in Question Start
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function (e) {return e[0]})
for (var i = rows; i >0; i--) {
if (firstColumn.indexOf(firstColumn[i-1]) != i-1) {
sheet.deleteRow(i);
}
}
}
//Code in Question End
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Refresh');
sheet.getRange('A2:K100').clearContent()
}
If new rows at the top of the sheet are found to be a duplicate, delete the new rows at the top.
try this:
function removeDuplicates() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows=sheet.getLastRow();
var firstColumn=sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function(e){return e[0]})
var uA=[];
for (var i=rows;i>0;i--) {
if (uA.indexOf(firstColumn[i-1])!=-1) {
sheet.deleteRow(i);
}else{
uA.push(firstColumn[i-1]);
}
}
}
I want to check values from a JSON URL against values within a defined range in my Google Sheet.
Should there be a match, I would like to query the properties of the cell containing the matching value (its row, its column, etc.)
How can I do this within Google Apps Script?
Everything you need to know about Spreadsheet operations in Apps Script can be found in Overview and SpreadsheetApp. You can start from there. For example, the methods to query properties like row and col are getRow and getColumn
Never mind. Got to my answer myself.
var i = 0;
// Bringing in the data from the third-party into Google Sheets.
var groupJSON = UrlFetchApp.fetch('<ANY-JSON-URL>');
var groupObjectRaw = JSON.parse(groupJSON);
// var groupObject = groupObjectRaw[0]; <--optional, for my use only
var membersGroupForm = groupObject.data['member'];
var projectsGroupForm = groupObject.data['project'];
var hoursGroupForm = groupObject.data['hours worked'];
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Details');
var membersRange = ss.getRange('Details!A:A');
var membersSheet = membersRange.getValues();
var projectsRange = ss.getRange('Details!1:1');
var projectsSheet = projectsRange.getValues();
function getRowNumber() {
for (i; i < membersSheet.length; i++) {
if (membersSheet[i][0] == membersGroupForm) {
return i + 1;
}
}
}
var rowNumber = getRowNumber(i, membersSheet, membersGroupForm);
Logger.log(rowNumber);
function getColumnNumber() {
for (var row in projectsSheet) {
for (var col in projectsSheet[row]) {
if (projectsSheet[row][col] == projectsGroupForm) {
return parseInt(col) + 1;
}
}
}
}
var columnNumber = getColumnNumber(projectsSheet, projectsGroupForm);
Logger.log(columnNumber);
var cell = ss.getRange(rowNumber, columnNumber);
cell.setValue(hoursGroupForm);
I want to prevent changes to column K in google spreadsheet. Whatever value is there, I do not want it changed. I do not like the protection feature as it makes what I consider an ugly display.
My code. Unfortunately, it does absolutely nothing. The intent was to take whatever the current value is in the cell, save it, and then write it back on exit of the cell instead of saving whatever changes might have been made to the cell. The cell will either be blank to start, or will already have been modified to contain a date & time. Whatever the current contents blank or not, it should retain the same value after leaving the cell.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
var r = s.getActiveCell();
var columnNum=r.getColumn()
// if column is K then prevent any changes
if (columnNum == 11) {
var dateCell = s.getRange(r.getRow(), 11);
var v=dateCell.getValue();
dateCell.setValue(v);
}
};
This might help you. A workaround to your problem I came up with till Google have a proper script API to protect ranges. This one is using the validation function and it works.
function setRangeProtection(rangeToProtect) {
var firstRow = rangeToProtect.getRow();
var noOfRows = rangeToProtect.getHeight();
var firstColumn = rangeToProtect.getColumn();
var noOfColumns = rangeToProtect.getWidth();
var rangeToProtectValues = rangeToProtect.getValues();
var rangeToProtectFormulas = rangeToProtect.getFormulas();
var cellRow = firstRow;
for (var i=0 ; i<noOfRows ; ++i) {
var cellColumn = firstColumn;
for (var j=0 ; j<noOfColumns ; ++j) {
var cell = sheet.getRange(cellRow, cellColumn);
if (rangeToProtectFormulas[i][j] == "") {
var rangeToProtectContent = rangeToProtectValues[i][j];
} else {
var rangeToProtectContent = rangeToProtectFormulas[i][j];
}
var rules = SpreadsheetApp.newDataValidation()
.requireTextEqualTo(rangeToProtectContent)
.setAllowInvalid(false)
.setHelpText('Don\'t mess with the DJ!')
.build();
cell.setDataValidation(rules);
++cellColumn;
}
++cellRow;
}
}
The only small issue with this is that with cells that contain formulas a small red triangle appears and a message is displayed when you hover over it. Couldn't get rid of that one. If you find a solution for that let me know. Removing the help text doesn't help as it returns to a default mode.