I am trying to modify an existing Google Sheet using a Form response sheet. Suppose in the sheet I need modified I have column G called "Annotated Asset ID" which contains a list of asset IDs. Now in the form response sheet there are two columns; Old ID (D) and New ID (E). I would like to check Column G of the existing sheet to see if it contains the Old ID and if it does I need to replace it with the New ID.
What is a good way to do this?
Form Response:
Existing Sheet:
Answer:
You can do this with Apps Script.
Steps:
Extract out the old and new IDs from the form response sheet
For each of the old IDs, use a TextFinder to search your column for the old ID in the existing sheet:
const cell = sheet.getRange("G1:G").createTextFinder("old-id").findNext()
Replace the ID if cell isn't null:
if (cell) { cell.setValue("new-id") }
Code Example:
function replaceIds() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const formResponseSheet = ss.getSheetByName("response-sheet-name")
const existingSheet = ss.getSheetByName("existing-sheet-name")
const oldIds = formResponseSheet.getRange("D1:D").getValues().flat()
const newIds = formResponseSheet.getRange("E1:E").getValues().flat()
const rangeToCheck = existingSheet.getRange("G1:G")
oldIds.forEach(function(id, index) {
let cell = rangeToCheck.createTextFinder(id).findNext()
if (cell) {
cell.setValue(newIds[index])
}
})
}
Add this to your project from the Tools > Script editor menu item. Make sure to change the response sheet nad existing sheet names.
Save the script and run the function. You will need to authorise it to run on your behalf.
Related
On my Google Drive I have a folder containing 2006 files.
These files are sorted by names. (from A -> Z)
Here's the exact order of the files inside my folder: https://gist.github.com/cuzureau/a36deb8e02ab0f2e0523cd58f2cf0950
Now, I also have a Google Sheet with the same file names written in a column. One file name per cell. They are order alphabetically (from A -> Z)
Here's the exact order of the files inside the column: https://gist.github.com/cuzureau/6fe722b821ce3aa60b697819b64d6b05
The files names are not sorted in the same order.
How can I do that ?
Maybe I should use a formula in Google Sheets to reorder the files names in the column ?
These are two possible solutions using Google Apps Script (both tested with the first 100 files):
Grab the names directly using DriveApp
Enter in your sheet and click on Extensions > Apps Script
Copy the folder id where the files lives
Copy this script.
const sheet = SpreadsheetApp.getActiveSheet()
function myFunction() {
const folderId = "<FOLDER_ID>"
// Get all files in the folder
const files = DriveApp.getFolderById(folderId).getFiles()
const toShortFiles = []
while (files.hasNext()) {
// save all the names
const file = files.next()
toShortFiles.push(file.getName())
}
// short A-Z
const shorted = toShortFiles.sort((a, b) => a.localeCompare(b)).map(n => [n])
sheet.getRange(1, 1, shorted.length, 1).setValues(shorted)
}
Short directly creating a new sheet
This is an alternative if you have more data in the same row
function shortRows() {
/* Create a new sheet */
const shortedSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('shortedSheet')
const range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn())
const values = range.getValues()
/* Short by name */
const result = values.sort((a, b) => {
return a[0].toString().localeCompare(b[0].toString())
})
/* Paste the data in the new sheet */
shortedSheet.getRange(1, 1, result.length, result[0].length).setValues(result)
}
If you don't want to create a new sheet, you have available the methods moveTo and moveRows I invite you to test them.
Documentation:
String.prototype.localeCompare()
setValues(values)
Class Sheet
I currently use a Google sheet to hold data x1 row per item. I use one particular cell to hold two separate hyperlinks within (x2 website addresses), both links are relevant/in relation to this item.
I want to keep the structure of having two separate clickable hyperlinks in one cell, but I also need to import this cell data (these two links) into another Google sheet, is there a way of using IMPORTRANGE and retaining these two separate hyperlinks (ensuring clickable & still x2 separate links within one cell), or converting them into hyperlinks when importing into another sheet?
Thank you in advance
I've created two dummy sheets with data for testing & to help visualise
Sheet Name: "Static" & Sheet URL: https://docs.google.com/spreadsheets/d/1JS40eNGUAmBqQJqmdX4PhWtm6GEVQP64CoxO_DooZYM/edit#gid=0
Sheet Name: "Imported" & Sheet URL: https://docs.google.com/spreadsheets/d/1rhnULIcbkSMCp8AONa7UwTQz77uHn443VuwYjty2xFs/edit#gid=0
I've used =IMPORTRANGE("1JS40eNGUAmBqQJqmdX4PhWtm6GEVQP64CoxO_DooZYM","Static1!A1:D")
To pull data from 'Static' sheet (tab: 'Static1') into 'Imported' (tab: 'Imported1')
I'm hoping to get clickable links in column 'D' of the 'Imported' sheet
I've added different variations i.e. the hyperlinks are renamed in 'Static' sheet as "Link 1" & "Link 2", I've added a few rows with full URLs addresses (no re-naming), and a couple with full URLs and with an empty line in between - I'm not too fussed with how they look to be honest (ideally it would be nice to have 'Link 1' & 'Link 2') but mainly just looking to have x2 imported URLs within same cell that remain/become clickable after importing
This is because I'll also be iframe/embedding the 'Imported' sheet afterwards.
Thank you
You can extract the urls by a custom function in addition of the IMPORTRANGE
function extractUrls(range) {
var activeRg = SpreadsheetApp.getActiveRange();
var activeSht = SpreadsheetApp.getActiveSheet();
var activeformula = activeRg.getFormula();
var rngAddress = activeformula.match(/\((.*)\)/).pop().trim();
var urls = activeSht
.getRange(rngAddress)
.getRichTextValue()
.getRuns()
.reduce((array, e) => {
var url = e.getLinkUrl();
if (url) array.push(url);
return array;
}, []);
return ([urls])
}
edit
in your situation
function createCopy() {
var id = "1JS40eNGUAmBqQJqmdX4PhWtm6GEVQP64CoxO_DooZYM"
var source = SpreadsheetApp.openById(id).getSheets()[0]
var values = source.getRange(2,1,source.getLastRow()-1,3).getValues()
var richTxt = source.getRange(2,4,source.getLastRow()-1,1).getRichTextValues()
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('output_v2')
output.getRange(2, 1, values.length, values[0].length).setValues(values)
output.getRange(2, 4, richTxt.length, richTxt[0].length).setRichTextValues(richTxt)
}
UPDATE:
Option 1:
This is fairly straightforward. It just copies the Static1 sheet unto a destination sheet and names it Imported.
function createCopy2() {
var ss = SpreadsheetApp.openById("1vM-0nBBlhVvRQ3vvXwkWlecENM7CVuv8iei3cl0uRQI");
var ds = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Static1');
sheet.copyTo(ds).setName('Imported');
}
Option 2 (incomplete):
This is what I have done so far, but this still throws an error Exception: Illegal Argument at this line var text = SpreadsheetApp.newRichTextValue().setText(linkval[i]).setLinkUrl(0,7,urls1[i]).setLinkUrl(7,13,urls2[i]).build();.
(P.S: Just wanted to throw this out here if in case someone would find this logic useful/not to waste my efforts practicing JS lol).
Kudos to Mike Steelson for providing a working answer.
function createCopy() {
//Creates a full copy of the source sheet without Hyperlinks (AKA importrange in script form)
var ss = SpreadsheetApp.openById("1vM-0nBBlhVvRQ3vvXwkWlecENM7CVuv8iei3cl0uRQI");
var sheet = ss.getSheets()[0].getSheetValues(1,1, ss.getLastRow(), ss.getLastColumn());
var ds = SpreadsheetApp.getActiveSheet();
ds.getRange(1,1, ss.getLastRow(), ss.getLastColumn()).setValues(sheet);
Logger.log(sheet);
var link = ds.getRange(2,4, ss.getLastRow(), 1);
var linkval = link.getValues();
var urls1 = ds.getRange(2,5, ss.getLastRow(), 1).getValues();
var urls2 = ds.getRange(2,6, ss.getLastRow(), 1).getValues();
for(i=0; i < ss.getLastRow(); i++){
var text = SpreadsheetApp.newRichTextValue().setText(linkval[i]).setLinkUrl(0,7,urls1[i]).setLinkUrl(7,13,urls2[i]).build();
}
}
I found code to list the names of all the sheets in Google Sheets
function sheetnames() {
var out = new Array()
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets()
for (var i=2 ; i<sheets.length ; i++) out.push( [ sheets[i].getName() ] )
return out
}
But we must place =sheetnames() in a cell to get value.
Is there any improvement in the script to automatically update when creating a new sheet?
You have to install an onChange trigger, which runs a function whenever a user modifies the structure of the spreadsheet—for example, when a sheet is inserted or removed.
You can install the trigger manually, following these steps, or programmatically, executing this function once:
function createOnChangeTrigger() {
const ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("writeSheetNames")
.forSpreadsheet(ss)
.onChange()
.create();
}
Once this is installed, the function writeSheetNames will run every time a sheet is created, removed, or renamed. This function should do two things:
Retrieve the sheet names of all the sheets in the spreadsheet.
Write these sheet names to the cell you indicate.
For example, the function below would write the data to column A of the sheet named Sheet1.
Code sample:
function writeSheetNames() {
const ss = SpreadsheetApp.getActive();
const sheetNames = ss.getSheets().map(sheet => [sheet.getName()]);
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A:A").clear(); // Delete previous data
sheet.getRange(1,1,sheetNames.length).setValues(sheetNames);
}
Reference:
onChange
I have a Google sheet that is the answer sheet to a form sent to students for registration purposes. A= Timestamp B= Email address of student c= Students answer D= All emails of invited students What i need to achieve is column B to check if the email exists in column D and if so move the cell to column E. If it moves it would leave a gap so I would need it to move and then move cells up by 1. I can then import the cells into another sheet to see only the students who have not replied or is there a way to email those automatically.
Your assistance would be greatly appreciated
https://docs.google.com/spreadsheets/d/1XfyKP5EbxoGYOiWgPiRWA_OvPGN9hOjICXp_iuJrwIg/edit?usp=sharing
You want to do the following:
Remove values from column D that also exist in column B (shift up values so that no blank cells are kept).
Send emails to the resulting column D values.
If that's the case, you can do the following with Apps Script: create a bound script by selecting Tools > Script editor and copy and execute the following code (check inline comments):
function sendEmailsToNonRespondents() {
var ss = SpreadsheetApp.getActive(); // Get spreadsheet
var sheetName = "Sheet1"; // Your sheet name (change if necessary)
var sheet = ss.getSheetByName(sheetName); // Get sheet
var allAddresses = getColumnValues(sheet, 2, 4); // Get non-empty values from column D (excluding row 1)
var respondents = getColumnValues(sheet, 2, 2); // Get non-empty values from column B (excluding row 1)
var nonRespondents = allAddresses.filter(address => !respondents.includes(address)).map(address => [address]); // Retrieve list of emails of non-respondents
sheet.getRange("D2:D").clearContent(); // Clear old column D content (excluding row 1)
sheet.getRange(2, 4, nonRespondents.length, 1).setValues(nonRespondents); // Write new column D content (non-respondents)
nonRespondents.forEach(email => {
var subject = "Mail subject"; // Change according to your preferences
var body = "Mail body"; // Change according to your preferences
MailApp.sendEmail(email[0], subject, body); // Send email for each non-respondent
});
}
function getColumnValues(sheet, firstRow, colIndex) {
return sheet.getRange(firstRow, colIndex, sheet.getLastRow() - firstRow + 1, 1).getValues().filter(value => value[0] != "").map(value => value[0]);
}
Note:
In this sample, no data is moved to column E (I don't see why that's necessary, since the respondent emails are recorded in column B anyway).
In this sample, every time the script runs, all content in column D gets removed, and the filtered content is written again, instead of just removing the undesired values.
Reference:
Spreadsheet Service
MailApp.sendEmail(recipient, subject, body)
I'm trying to create a dynamic HYPERLINK formula that will automatically create the link based on the sheet name in Column A, but I'm not sure how (or if it's possible) to get the URL of the sheet based on the name.
Here's the setup:
Single Google Spreadsheet with multiple tabs
Tab names: 1500, 1501, 1502, Consolidated
On the Consolidated tab, I have two columns: Column A is the Sheet Name, and Column B is a HYPERLINK formula, that when clicked should open the corresponding sheet.
Is there a way to programmatically get the URL for the sheet based on the sheet name in Column A? Perhaps I could use a script to populate Column C with the URL, then use the following formula: =HYPERLINK(C2,A2)?
Thanks for the help!
Surprised to see this as a top search on Google but with no answer.
Anyway, here's the method I found that works for me: combine Hyperlink with values from a different column using the &, a basic example is shown below:
If you are trying to automatically generate direct URL's to specific sheets based on their name, and do not want to use scripts, you are out of luck. Currently, the only way to link directly to specific sheets is by appending the correct gid number to the spreadsheet URL. A gid must be either copied manually from the active sheet's URL, or automatically extracted with a custom function, created using scripts.
Since you're open to using scripts, it looks like i found a detailed tutorial of how to do this. https://www.benlcollins.com/spreadsheets/index-sheet/
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Index Menu')
.addItem('Create Index', 'createIndex')
.addItem('Update Index', 'updateIndex')
.addToUi();
}
// function to create the index
function createIndex() {
// Get all the different sheet IDs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var namesArray = sheetNamesIds(sheets);
var indexSheetNames = namesArray[0];
var indexSheetIds = namesArray[1];
// check if sheet called sheet called already exists
// if no index sheet exists, create one
if (ss.getSheetByName('index') == null) {
var indexSheet = ss.insertSheet('Index',0);
}
// if sheet called index does exist, prompt user for a different name or option to
cancel
else {
var indexNewName = Browser.inputBox('The name Index is already being used,
please choose a different name:', 'Please choose another name',
Browser.Buttons.OK_CANCEL);
if (indexNewName != 'cancel') {
var indexSheet = ss.insertSheet(indexNewName,0);
}
else {
Browser.msgBox('No index sheet created');
}
}
// add sheet title, sheet names and hyperlink formulas
if (indexSheet) {
printIndex(indexSheet,indexSheetNames,indexSheetIds);
}
}
// function to update the index, assumes index is the first sheet in the workbook
function updateIndex() {
// Get all the different sheet IDs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var indexSheet = sheets[0];
var namesArray = sheetNamesIds(sheets);
var indexSheetNames = namesArray[0];
var indexSheetIds = namesArray[1];
printIndex(indexSheet,indexSheetNames,indexSheetIds);
}
// function to print out the index
function printIndex(sheet,names,formulas) {
sheet.clearContents();
sheet.getRange(1,1).setValue('Workbook Index').setFontWeight('bold');
sheet.getRange(3,1,names.length,1).setValues(names);
sheet.getRange(3,2,formulas.length,1).setFormulas(formulas);
}
// function to create array of sheet names and sheet ids
function sheetNamesIds(sheets) {
var indexSheetNames = [];
var indexSheetIds = [];
// create array of sheet names and sheet gids
sheets.forEach(function(sheet){
indexSheetNames.push([sheet.getSheetName()]);
indexSheetIds.push(['=hyperlink("#gid='
+ sheet.getSheetId()
+ '","'
+ sheet.getSheetName()
+ '")']);
});
return [indexSheetNames, indexSheetIds];
}