I would like a Function in the Script attached to the Spreadsheet, to iterate through all the Named Ranges, and enter them in cells in a "Data" sheet. This sheet has lots of lists etc, which are used by my main sheet ("Analysis").
Ideally, this would put them starting at Row 1001, with col A being the Name, and col D being the Range. (Cols B & C left blank to allow for Overflow from col A - don't want to resize col A)
To be effective, any existing data in the rows previously entered by the Function should first be deleted, before updating with the new data.
Although I'm getting the hang of the Script language, I'm struggling with this, so if anyone could provide an example function, it would be appreciated! MTIA.
[edit]
Apologies if my original question was inadequate. I had some code, but it was rubbish, I knew it, so I scrapped it. Almost there now (I think) but one more prob to sort, getting an exception:
function namedRangeToSpreadsheet() {
// Author: Cooper
// Link: https://stackoverflow.com/a/64841828/190925
// Purpose: Record all NamedRanges by both Name and Range, as a check that
// public copy is NOT missing any!
const ss=SpreadsheetApp.getActive();
const nrA=ss.getNamedRanges();
var startRow = 1001; // MH insert from this row
var endRow = 1200; // MH clear|insert to a max. of this
// var targetSh = "Data"; // MH revert to Data ss when tests OK
var targetSh = "Named Ranges";
// MH clear previous data
let sh=ss.getSheetByName(targetSh);
var range = sh.getRange(targetSh + "!A" + startRow + ":D" + endRow);
range.clear();
//sh.getRange(sh.getLastRow()+1,1,rowA.length,4).clear();
let rowA=[];
for(let i=0;i<nrA.length;i++) {
let name=nrA[i].getName();
let range=nrA[i].getRange().getA1Notation();
rowA.push([name,'','',range]);
}
rowA.sort(); // MH
/* Exception: The number of rows in the data does not match the number of rows in the range.
The data has 32 but the range has 200. (line 348, file "Code") */
//sh.getRange(sh.getLastRow()+1,1,rowA.length,4).setValues(rowA);
range.setValues(rowA);
}
function namedRangeToSpreadsheet() {
const ss=SpreadsheetApp.getActive();
const nrA=ss.getNamedRanges();
let rowA=[];
for(let i=0;i<nrA.length;i++) {
let name=nrA[i].getName();
let range=nrA[i].getRange().getA1Notation();
rowA.push([name,'','',range]);
}
let sh=ss.getSheetByName('Sheet1');
sh.getRange(sh.getLastRow()+1,1,rowA.length,4).setValues(rowA);
}
Related
sure I had this down but just can't get it right. Had a script set up to get data from one sheet and put it into another one, which I have done but it leaves gaps when copying and I can't figure out how to solve it. I'm sure in the code, its where I have put a question mark, is where the problem lies.
I have tried put i, last, last+1, 10, 12 but none of these work, feel like i'm missing something small to get this right. Below is the code a link to view the sheet if needed (the sheet is just for me to learn from, a basic example if you will).
Thanks in advance and also if the code could be better written, please just let me know as still learning this :)
function copyInfo() {
var app = SpreadsheetApp;
var copySheet = app.getActiveSpreadsheet().getSheetByName("Copy");
for (var i = 2; i <12; i++) {
var getInfo = copySheet.getRange(2,2,i,2).getValues();
// get the info from range above - start at row 2 on column 2 (b), get number of rows i , number of columns = 2, b,c
var last = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Paste").getLastRow();
var pasteSheet = app.getActiveSpreadsheet().getSheetByName("Paste");
// Tell it where you want the info to go to
pasteSheet.getRange(last+1,1,?,2).setValues(getInfo);
var clearIt = copySheet.getRange(2,2,i,2).clearContent();
// this clears the copy range aka getInfo
}}
link to sheet
You can copy whole range at once using copyTo, so your function could be rewritten as:
function copyInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("Copy");
var pasteSheet = ss.getSheetByName("Paste");
// get source range
var source = copySheet.getRange(2,2,12,2);
// get destination range
var destination = pasteSheet.getRange(pasteSheet.getLastRow()+1,2,12,2);
// copy values to destination range
source.copyTo(destination);
// clear source values
source.clearContent();
}
sure I had this down but just can't get it right. Had a script set up to get data from one sheet and put it into another one, which I have done but it leaves gaps when copying and I can't figure out how to solve it. I'm sure in the code, its where I have put a question mark, is where the problem lies.
I have tried put i, last, last+1, 10, 12 but none of these work, feel like i'm missing something small to get this right. Below is the code a link to view the sheet if needed (the sheet is just for me to learn from, a basic example if you will).
Thanks in advance and also if the code could be better written, please just let me know as still learning this :)
function copyInfo() {
var app = SpreadsheetApp;
var copySheet = app.getActiveSpreadsheet().getSheetByName("Copy");
for (var i = 2; i <12; i++) {
var getInfo = copySheet.getRange(2,2,i,2).getValues();
// get the info from range above - start at row 2 on column 2 (b), get number of rows i , number of columns = 2, b,c
var last = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Paste").getLastRow();
var pasteSheet = app.getActiveSpreadsheet().getSheetByName("Paste");
// Tell it where you want the info to go to
pasteSheet.getRange(last+1,1,?,2).setValues(getInfo);
var clearIt = copySheet.getRange(2,2,i,2).clearContent();
// this clears the copy range aka getInfo
}}
link to sheet
You can copy whole range at once using copyTo, so your function could be rewritten as:
function copyInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("Copy");
var pasteSheet = ss.getSheetByName("Paste");
// get source range
var source = copySheet.getRange(2,2,12,2);
// get destination range
var destination = pasteSheet.getRange(pasteSheet.getLastRow()+1,2,12,2);
// copy values to destination range
source.copyTo(destination);
// clear source values
source.clearContent();
}
This is what I want to do:
I have a source sheet called "BD Visitas".
This sheet contains some data, which will later be the data that I want to copy to another sheet.
I have a target sheet called "Ficha mascota".
This sheet contains a range of rows where I want to paste the data from the source sheet "BD Visitas".
The issue is the following:
On the destination sheet, in cell "D18" the name of the pet is written.
Take the value of this cell, perform a search in column B of the source sheet "BD Visitas" and look for the name matches.
After finding the matching rows, get the last 15 matches. That is, from the last row to the first row (from bottom to top).
When you get the last 15 matching rows, then copy the columns C, G, H, I (source sheet) and paste the values in the range of rows 39 to 53, in the columns C, D, G, M of the destination sheet "Ficha mascota".
However, it must be copied in reverse order, that is, the last match of the source sheet must be copied to row 39 of the destination sheet. The penultimate match of the source sheet must be copied to row 40 of the target sheet. And so on. Therefore, in row 53 of the target sheet the first match within the range of 15 matches will be pasted.
The work done that I have is the following:
function get15lastRowsBDvisitasToFichaMascota() {
const libro = SpreadsheetApp.getActiveSpreadsheet();
const hojaOrigen = libro.getSheetByName('BD visitas');
const hojaDestino = libro.getSheetByName('Ficha mascota');
// Rango destino
var rowsDestino = hojaDestino.getRange("C39:U53");
var rowsDestinoR = rowsDestino.getNumRows();
var rowsDestinoUF = rowsDestino.getLastRow();
// Coincidencia nombre mascota
var nombreVisor = hojaDestino.getRange('D18').getValue();
var tablaDeBusquedaNombre = hojaOrigen.getRange('B7:J').getValues();
var listaDeBusquedaNombre = tablaDeBusquedaNombre.map(function(row){return row[0]});
var busquedaNombre = listaDeBusquedaNombre.indexOf(nombreVisor);
var coincidenciaNombre = tablaDeBusquedaNombre[busquedaNombre][0];
var fechaVisita = tablaDeBusquedaNombre[busquedaNombre][1];
var motivoVisita = tablaDeBusquedaNombre[busquedaNombre][5];
var sintomasVisita = tablaDeBusquedaNombre[busquedaNombre][6];
var observacionesVisita = tablaDeBusquedaNombre[busquedaNombre][7];
// Rangos copia y pega
var obj = [
{ src: fechaVisita, dst: "C" + rowsDestinoUF }, // Fecha
{ src: motivoVisita, dst: "D" + rowsDestinoUF }, // Motivo
{ src: sintomasVisita, dst: "G" + rowsDestinoUF }, // Sintomas
{ src: observacionesVisita, dst: "M" + rowsDestinoUF }, // Observaciones
];
Logger.log(obj);
obj.forEach(({src, dst}) => hojaOrigen.getRange(src).copyTo(hojaDestino.getRange(dst), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false));
}
But I am stuck. I do not know how to call to paste the fixed ones of the source sheet within the range of rows of the destination sheet.
I also don't know how to get the rows to be pasted on the destination sheet in reverse order.
I am new to coding with google apps script and javascript. I have learned a lot in a few days and I have been able to do many things, however, my capacity is still limited and I miss a lot.
Any help is well appreciated. Thannk you very much.
Sorry for my poor English.
It seems you're trying to search for the pet name using indexOf(), but that would just bring the first match and, from what I understand, you want to get up to 15 matches.
One way of doing that would be:
Iterate over the rows of the source sheet and check if the name in column B matches the one specified by cell D18 in the destination sheet. Since you want to get them in reverse order, you could start from the last row and go up until you get to the desired number (15) or until you check all values.
When there is a match, you can get the values from the columns you want and store them in an array.
Next, you write the values to the destination spreadsheet. Since your destination columns are not all consecutive and supposing you don't want to delete any existing data in the middle of the range, using setValues() probably wouldn't help in this case. One alternative would be to write the values one by one using setValue(), but that isn't very efficient. Perhaps a better way would be to write the values column by column, as the rows are consecutive.
The above can be implemented like this:
function get15lastRowsBDvisitasToFichaMascota() {
const libro = SpreadsheetApp.getActiveSpreadsheet();
const hojaOrigen = libro.getSheetByName('BD visitas');
const hojaDestino = libro.getSheetByName('Ficha mascota');
// Rango destino
var rowsDestino = hojaDestino.getRange("C39:U53");
var rowsDestinoR = rowsDestino.getNumRows();
var rowsDestinoUF = rowsDestino.getLastRow();
// Coincidencia nombre mascota
var nombreVisor = hojaDestino.getRange('D18').getValue();
var tablaDeBusquedaNombre = hojaOrigen.getRange('B7:J' + hojaOrigen.getLastRow()).getValues();
var maxMascotas = 15;
var dadosMascotas = [];
for (var i = tablaDeBusquedaNombre.length - 1; dadosMascotas.length <= maxMascotas && i >= 0; i--) {
if (tablaDeBusquedaNombre[i][0] === nombreVisor) {
var fechaVisita = tablaDeBusquedaNombre[i][1];
var motivoVisita = tablaDeBusquedaNombre[i][5];
var sintomasVisita = tablaDeBusquedaNombre[i][6];
var observacionesVisita = tablaDeBusquedaNombre[i][7];
dadosMascotas.push([fechaVisita, motivoVisita, sintomasVisita, observacionesVisita]);
}
}
if (dadosMascotas.length === 0) {
return;
}
var hojaDestinoCols = ['C', 'D', 'G', 'M'];
var primeraRowDestino = rowsDestino.getRow()
for (var i = 0; i < hojaDestinoCols.length; i++) {
// Get the values per column
var colValues = dadosMascotas.map(function (row) { return [row[i]] });
// Write the column values starting from the first row of the destination range
hojaDestino.getRange(
hojaDestinoCols[i] + primeraRowDestino +
':' +
hojaDestinoCols[i] + (primeraRowDestino + dadosMascotas.length - 1))
.setValues(colValues);
}
}
Disclaimer: I'm a Google Apps Script newbie.
I'm trying to create a timesheet in Google Sheets that lets a user clock in & clock out to log hours on a given project. I've borrowed code from a YouTube video on the general structure of setting the whole thing up.
Here's what the blank time sheet looks like. It's pretty basic:
I've created a user button (off to the right) where the user presses "Start" and cell A2 will input a timestamp. Then the user can press an "End" button, and a second timestamp, this time in B2, will appear, along with a simple calculation in C2 that measures the delta in the two timestamps, thus giving a duration of time spent on a given task or project. Here's what it looks like:
When the user needs to press "Start" again, a new timestamp appears in cell A3, and so on so forth, along with a new delta calculation for each new row.
Problem: I'm unable to get the simple delta calculation in column C to increment down each new rows so that the setFormula function doesn't contain hardcoded references to cells A2 & B2. See below code for what I have so far:
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue();
}
function getNextRow() {
return SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
}
function addStartRecord (a) {
var row = getNextRow();
setValue('A' + row, a);
}
function addEndRecord (b, c) {
var row = getNextRow()-1;
setValue('B' + row, b);
setValue('C' + row, c);
}
function punchIn() {
addSRecord(new Date());
}
function punchOut() {
addERecord(new Date(), '=B2-A2');
}
The problem is with the punchOut() function there at the bottom. Any idea on the best way to increment this delta calculator down each new row?
Note: I saw a pretty good answer to a similar question here, but the code is throwing an error in the script editor after the line containing data[i] = ['=A' + i+1.toString() + ' + 1 ' ]. Also, I don't want to set a definitive last row for the delta calculation (such as 20 in this example). I'd want the user to be able to record as many new start/end times for a project as they'd want.
Edit: Here's a link to the timesheet so you can test the code.
Try modifying your punchOut method like this:
function punchOut() {
var ss = SpreadsheetApp.getActiveSheet();
var row = ss.getLastRow();
addEndRecord(new Date(), '=B' + row + '-A' + row);
}
I tested it in the sheet and it worked well.
setFormula() - this enables you to describe the formula to be inserted into column C.
The following is two simple functions that handle "Punch in" and "Punch Out" (with its calculation).
function so5695101401in() {
// punchin
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
// Logger.log("DEBUG: the last row is "+lR);
var punchinRange = sheet.getRange(lR+1, 1);
// Logger.log("DEBUG: the punchinRange = "+punchinRange.getA1Notation());
punchinRange.setValue(new Date());
}
function so5695101401out() {
// punchout
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
//Logger.log("DEBUG: the last row is "+lR);
var punchoutRange = sheet.getRange(lR, 2);
// Logger.log("DEBUG: the punchoutRange = "+punchoutRange.getA1Notation());
punchoutRange.setValue(new Date());
var timeElapsed = sheet.getRange(lR, 3).setNumberFormat("hh:mm:ss");
timeElapsed.setFormula("=B2-A2");
}
setFormula
I use a workaround for this problem, via app script copy the cell with the formula to de new row or range!.
for you problem:
var formula1 = sheetDatos.getRange(lastRow, 3); //get the formula
var copyRange = sheetDatos.getRange(lastRow+1, 3);
formula1.copyTo(copyRange);
for me is more easy in this way, try to do in sheet to understand how this work.
you need a initial formula to go in this way ;)
I've been having a hard time trying to figure this out. I realize this is perhaps more basic than usual for those who follow the GAS tag, however any help much appreciated.
If I'm breaking up my bigger task into component parts, my goal right now with this question is to update several named ranges automatically.
There is a tab on the spreadsheet called "DataImport". DataImport has 10 columns all 1000 rows long. There is a named range for each column e.g. cabbages (A2:A1000), dogs (B2:B1000) etc etc.
There is a script attached to a new menu item "Update Data" that when selected imports a csv file into DataImport tab meaning that the length of the data set will grow.
How can I tell the sheet to update each named range to be the length of data? So if the original named range "cabbages" was A2:A1000 and following an update the data now actually goes as long as A2:A1500, how would I tell the sheet to update the range cabbages?
I found a snippet of code online and started to fiddle with it:
function testNamedRange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange('DataImport!A:A');
var data_len = range.length;
SpreadsheetApp.getUi().alert(data_len); // "alert just gives "undefined"
ss.setNamedRange('TestRange', range);
var rangeCheck = ss.getRangeByName('TestRange');
var rangeCheckName = rangeCheck.getA1Notation();
}
My thinking was if I could just get the length of data following an update using the custom menu function, I could then use setNamedRange() to update cabbages range.
I'm really lost and I imagine this is simpler than I'm making it out to be.
How can I update the named range cabbages to be the length of data in UpdateData column A?
Edit: IMPORTANT
Use INDIRECT("rangeName") in formulas instead of just rangeName.
The only way to extend the range programmatically is by removing it and then adding it back with a new definition. This process breaks the formula and returns #ref instead of the range name. This should be an unnecessary work around. if you agree please star and the issue tracker at: https://code.google.com/p/google-apps-script-issues/issues/detail?id=5048
=sum(indirect("test1"),indirect("test3"))
Emulates open ended named ranges by checking to see that the last row in the named range is the same as the last row in the sheet. If not, adjusts the named range so the last row in the named range is the same as the last row in the sheet.
should probably be used with on open and on change events.
function updateOpenEndedNamedRanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// names of open-ended ranges
var openEndedRangeNames = ["test1", "test2", "test3", "s2test1" ];
for(i in openEndedRangeNames) {
var rName = openEndedRangeNames[i];
try{
var r = ss.getRangeByName(rName);
}
catch(err) {
GmailApp.sendEmail("me#gmail.com",
rName + " -- Cannot find",
"Trying to update open-ended ranges after rows added. \n"
+ "Unable to find range name-- "+ rName
+ " -- in ss ( " + ss.getName() + " ) "
+ "\n If it is not needed please remove it "
+ "from array \n openEndedRangeNames[] \n in the function \n"
+ "updateOpenEndedNamedRanges()");
continue;
}
var rlr = r.getLastRow();
var s = r.getSheet();
var slr = s.getMaxRows();
if(rlr==slr ) continue;
var rfr = r.getRow();
var rfc = r.getColumn();
var rnc = r.getNumColumns();
var rnr = slr - rfr + 1;
ss.removeNamedRange(rName);
ss.setNamedRange( rName, s.getRange(rfr, rfc, rnr, rnc ));
}
}
function ssChangeEvent(change) {
// changeType (EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW,
// REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, or OTHER)
switch(change.changeType) {
case "INSERT_ROW":
updateOpenEndedNamedRanges();
break;
default:
Logger.log(change.changeType + " detected. No action taken ");
}
}
Setup ssChangeEvent(change) to run when rows are added
Resources>this projects triggers
Offering this function I wrote to handle dynamic resize of named ranges:
function resizeNamedRange(rangeName, addRows, addCols) {
/* expands (or decreases) a range of a named range.
rows and columns to add can be negative (to decrease range of name). Params:
rangeName - name of range to resize.
addRows - number of rows to add (subtract) from current range.
addCols - number of columns to add (subtract) from current range.
Call example: resizeNamedRange("Products",1,0);
*/
var sh = SpreadsheetApp.getActiveSpreadsheet();
try {
var oldRange = sh.getRangeByName(rangeName);
var numRows = oldRange.getNumRows() + addRows;
var numCols = oldRange.getNumColumns() + addCols;
if (numRows < 1 || numCols <1) {
Logger.log("Can't resize a named range: minimum range size is 1x1.");
return;
}
sh.setNamedRange(rangeName, oldRange.offset(0,0,numRows, numCols));
} catch (e) {
Logger.log ("Failed resizing named range: %s. Make sure range name exists.", rangeName);
}
}
Maybe I'm missing something, but the function below takes a rangename and the range that it should contain. If the rangename already exists it updates the range to the passed value. If the rangename doesn't exist, it creates it with the passed range values.
Also with regard to the "#REF!" problem in the sheet. You can do a find and replace and tick the box for "find in formulas". Put "#REF!" in find and the named range name in the replace box. This assumes only one named range was deleted and that there were no other unrelated #REF! errors. This approach helped me fix a spreadsheet with the error spread over 8 different sheets and 20+ formulas in just a few minutes.
/**
* Corrects a named range to reflect the passed range or creates it if it doesn't exist.
*
* #param {string} String name of the Named Range
* #param {range} Range (not string, but range type from Sheet class)
* #return {void || range} returns void if Named Range had to be created, returns NamedRange class if just updated. This needs improvement.
* #customfunction
*/
function fixNamedRange (name, range) {
var i = 0;
var ss = SpreadsheetApp
.getActiveSpreadsheet();
var ssNamedRanges = ss.getNamedRanges();
for (i = 0; i<ssNamedRanges.length && ssNamedRanges[i].getName() != name; i++) {};
if (i == ssNamedRanges.length) {
return (ss.setNamedRange(name, range));
} else {
return (ssNamedRanges[i].setRange(range));
}
}
I found the solution!
I have a cell with a drop down list with all the clients that the company has registered on the system, if the name we enter does not appear on the list, then function newClient executes. Basically we use the SpreadsheetApp.getUi() in order to save the new information. Once we have introduced the client data, function creates a new row on the client's sheet and enters the information from the prompts on the last row. Once done, updates the drop down list automatically.
The real function is inside of a big function that calls newClient if it's needed so the real one would be newClient(client, clients), on the example I put the variables in order to make it easier.
I hope it works!
function newClient() {
var ss = SpreadsheetApp.getActive().getSheetByName('Clients'); // Sheet with all the client information, name, city, country...
var client = 'New company';
var clients = ss.getRange('A2:A').getValues();
var ui = SpreadsheetApp.getUi();
ui.alert('Client '+client+' does not exist, enter the next information.');
var city = ui.prompt('Enter city').getResponseText();
var country = ui.prompt('Enter country').getResponseText();
client = client.toUpperCase();
city = city.toUpperCase();
country = country.toUpperCase();
ui.alert('Here is the information you entered about '+client+':'+'\n\n'+'City: '+city+'\n\n'+'Country: '+country)
ss.insertRowAfter(ss.getLastRow()); // Insert a row after the last client
ss.getRange('A'+(clients.length+2)).setValue(client); // Let's suppose we have 150 clients, on the first row we have the titles Client, City, Country, then we have the 150 clients so the last client is on row 151, that's why we enter the new one on the 152
ss.getRange('B'+(clients.length+2)).setValue(city);
ss.getRange('C'+(clients.length+2)).setValue(country);
var namedRanges = SpreadsheetApp.getActive().getNamedRanges(); // We get all the named ranges in an array
for (var i = 0; i < namedRanges.length; i++) {
var name = namedRanges[0].getName();
if (name == 'Clients') { // All the clients are stored on 'Clients' named range
var range = ss.getRange('A2:A'); // Update the range of the 'Clients' named range
namedRanges[i].setRange(range);
}
}
ui.alert('Client created, you can find it on the drop down list.');
}