Dynamic Hyperlink based on cell contents in google sheets? - google-apps-script

There is a list of items in a google spreadsheet, that we need to link a bunch of files from another folder on the google drive. The link is constructed with the function shown below:
function bscott () {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var s=ss.getActiveSheet();
var c=s.getActiveCell();
var fldr=DriveApp.getFolderById("**FOLDER ID - not included for security**");
var files=fldr.getFiles();
var names=[],f,str;
while (files.hasNext()) {
f=files.next();
str='=hyperlink("' + f.getUrl() + '","' + f.getName() + '")';
names.push([str]);
}
s.getRange(c.getRow(),c.getColumn(),names.length).setFormulas(names);
}
The problem we have encountered is if the contents of the folder do not match the ordering of our list exactly, the ordering of files to line items gets out of sync. We want certain files to hyperlink to certain cells in our sheet if the contents of another cell in the same row match the contents in the file name. We would like to avoid manually creating links for each line item. Is this possible in google sheets?

Create an object of file names to links:
var objNamesToLinks = {};//Create an empty object to be populated with data
Use your while loop to populate the object:
var fileName = "";//declare variable outside of the loop.
while (files.hasNext()) {
f=files.next();
fileName = f.getName();
str='=hyperlink("' + f.getUrl() + '","' + fileName + '")';
names.push([str]);
objNamesToLinks[fileName] = str;//The object key is the file name
};
The result of the object will be:
objNamesToLinks = {
'fileName1':'Link1',
'fileName2':'Link2',
'fileName3':'Link3'
};
Then get the column of data in your spreadsheet with the file names:
var columnWithFileNames = s.getRange(start Row, column with names, s.getLastRow())
.getValues();
Then loop through the array of data in the column and match the file names up. If there is a match, look up the correct link, and put it into the cell.
var thisFileName, thisMatchedLink;
for (var i=0;i<columnWithFileNames.length;i+=1) {
thisFileName = columnWithFileNames[i][0].trim();
thisMatchedLink = objNamesToLinks[thisFileName];
if (thisMatchedLink !== undefined) {
s.getRange(i,columnToPutLinkInto).setValue(thisMatchedLink);
};
};

Related

Searching specific folder within google drive base on cell value given in the Googlesheet and want to save its complete url in the googlesheet cell

I am using Google sheet to keep my data. The data is confidential and I can not share it here. The dummy data sample is looks like this.
The (1)- is registration number, which I want to look into the Google Drive. This registration number has the same folder name within the google drive that contains the additional information about the registration number. So, I want to find that folder within the google drive and once it found, I need to store the path of that folder in the sheet as given in the column "G" Photoreport. I have millions of data and I want to search this data row by row and keep the path of each row in the same column. If path not found there should be a statement which will show me that "Path Not Found". I have tried this code but is not working.
function SearchFiles() {
//Please enter your search term in the place of Letter
var searchFor ='title contains "Letter"';
var names =[];
var fileIds=[];
var files = DriveApp.searchFiles(searchFor);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
fileIds.push(fileId);
var name = file.getName();
names.push(name);
}
for (var i=0;i<names.length;i++){
Logger.log(names[i]);
Logger.log("https://drive.google.com/uc?export=download&id=" + fileIds[i]);
}
}
Please help me in this problem. Thanks
Find Folders
function findFolders() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet Name');
const vs = sh.getRange(2,1,sh.getLastRow() - 1,sh.getLastColumn()).getValues();
vs.forEach((r,i) => {
if(!r[6] && r[2]) {
let folders = DriveApp.getFoldersByName(r[2]);
let folder = [];
while (folders.hasNext()) {
folder.push(folders.next());
}
if((folder.length > 1)) {
sh.getRange(i + 2,7).setValue(`${folder.length} folders with name ${r[2]}`)
} else if(folder.length == 0 ) {
sh.getRange(i + 2, 7).setValue(`Folder name: ${r[2]} not found`);
} else if (folder.length == 1) {
sh.getRange(i + 2, 7).setValue(folder[0].getId());//I would prefer having the id of the folder because going to folder with the id is very easy with getFolderById();
}
}
});
}

Exporting big google sheets file as multiple csv with batch size 500

I would like to download a 10000 row long singular sheet from Google Sheets split into 20 separate csv files with batch size of 500 rows each. Could this be possibly done using Google Sheets?
I wasn't able to find a native support for sheets to download and separate the files into multiple parts/fragments. But I was able to find a Google Apps Script code that saves the sheet into a csv.
So what I did was modify the code and then make it turn a sheet into multiple csv's.
The catch is, it will upload all those files into a folder in the logged in google drive and then you can download them in a zip file. (I'll show this later below)
Here is the source code.
And here is the modified code I did:
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// Update numFiles into how many files you want is outputted
// The script will calculate how many rows it can place in a file equally
// Excess will be placed into an additional file
var numFiles = 5;
var rowsPerFile = Math.floor(sheet.getDataRange().getLastRow() / numFiles);
// create a folder from the name of the spreadsheet
var folder = DriveApp.createFolder(ss.getName().toLowerCase().replace(/ /g,'_') + '_csv_' + new Date().getTime());
// append ".csv" extension to the sheet name
fileName = sheet.getName();
// convert all available sheet data to csv format
var csvFiles = convertRangeToCsvFile_(sheet, rowsPerFile);
// create a file in the Docs List with the given name and the csv data
csvFiles.forEach(function(csvFile, i){
var csvName = fileName + '_' + (i + 1) + '.csv';
folder.createFile(csvName, csvFile);
});
}
function convertRangeToCsvFile_(sheet, rowsPerFile) {
// get available data range in the spreadsheet
var activeRange = sheet.getDataRange();
try {
var data = activeRange.getValues();
var csvFiles = [];
// loop through the data in the range and build a string with the csv data
if (data.length > 1) {
var csv = "";
for (var row = 0; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if (data[row][col].toString().indexOf(",") != -1) {
data[row][col] = "\"" + data[row][col] + "\"";
}
}
// join each row's columns
// add a carriage return to end of each row, except for the last one
if (row < data.length-1) {
csv += data[row].join(",") + "\r\n";
}
else {
csv += data[row];
}
if ((row + 1) % rowsPerFile === 0) {
csvFiles.push(csv);
csv = "";
}
}
}
// If there are remainders that doesnt reach the number of files needed.
// It will add another file, containing all remainder rows there.
// 10010 rows / 20 files = 10 rows excess
// 21st file will contain those 10 rows excess
// No excess means no 21st file
if(csv) {
csvFiles.push(csv);
}
return csvFiles;
}
catch(err) {
Logger.log(err);
}
}
Sample Data:
Sample Output:
Upon right clicking the folder created in Google Drive, you will have the option to download the whole folder as zip file containing all the csv files.
Here is the downloaded zip file containing all csv files:
Since there are 22 rows and the sample code wants 5 files, a 6th file was created containing the excess 2 rows

Search Google Drive for a file

I am trying to write a script to search my Google Drive for a file. The search should only look at the name of the file (not the contents), and look for a string within that name.
For example, there's a file called "2018-08-06_Miller_576132063_17.25.pdf" and I want to have my script search for "576132063" and get the fileID.
If it matters, I would be searching within subfolders of folder: "0B1-kfT4ZOAitb1lZSzM5YXR6czA"
function FileFinder() {
var ShowMeCC = '576132063';
var files = DriveApp.getFolderById('0B1-kfT4ZOAitb1lZSzM5YXR6czA').searchFiles('title contains "' + ShowMeCC + '" ');
while (files.hasNext()) {
var file = files.next();
var fnaMe = file.getName();
var fID = file.getId();
Logger.log(fID);
}
}
The search finds nothing.
This seems basic, yet I can't find anyone who asked this specific question.
Thanks in advance!
You want to retrieve files which have the filename including the string of ShowMeCC.
If my understanding is correct, how about this workaround?
The official document says as follows.
The contains operator only performs prefix matching for a title. For example, the title "HelloWorld" would match for title contains 'Hello' but not title contains 'World'.
By this, unfortunately, the file of 2018-08-06_Miller_576132063_17.25.pdf cannot be directly retrieved using title contains '576132063'. So as a workaround, it is considered the following workaround.
Search the file after all files were retrieved.
In this case, at first, it is required to retrieve all files. But this is the high cost. In order to reduce the cost, I would like to propose 2 step searching.
Retrieve files using the query of fullText contains '576132063'.
fullText contains '576132063' can search the filename like 2018-08-06_Miller_576132063_17.25.pdf.
Retrieve the file from files retrieved by fullText contains '576132063'.
By this flow, all files are not required to be retrieved. So the cost becomes lower than that of above method.
Modified script 1:
function FileFinder() {
var ShowMeCC = '576132063';
var files = DriveApp.searchFiles('fullText contains "' + ShowMeCC + '"'); // Modified
// OR var files = DriveApp.getFolderById('0B1-kfT4ZOAitb1lZSzM5YXR6czA').searchFiles('fullText contains "' + ShowMeCC + '"'); // Modified
while (files.hasNext()) {
var file = files.next();
var fnaMe = file.getName();
if (fnaMe.indexOf(ShowMeCC) > -1) { // Added
var fID = file.getId();
Logger.log(fnaMe);
Logger.log(fID);
}
}
}
Reference:
Search for Files
If this modification was not the result you want, I apologize.
Added:
As one more sample, if you want to retrieve the file from a specific folder like DriveApp.getFolderById('0B1-kfT4ZOAitb1lZSzM5YXR6czA'), you can also use the following script. When the number of files in the folder is not much, the cost of this method will not become high.
Modified script 2:
function FileFinderaaa() {
var ShowMeCC = '576132063';
var files = DriveApp.getFolderById('0B1-kfT4ZOAitb1lZSzM5YXR6czA').getFiles();
while (files.hasNext()) {
var file = files.next();
var fnaMe = file.getName();
if (fnaMe.indexOf(ShowMeCC) > -1) {
var fID = file.getId();
Logger.log(fnaMe);
Logger.log(fID);
}
}
}

How to insert in a cell the name and link of a folder that has just been created in Google Drive?

I'd like to create some folders in Google Drive through a script in Google Sheets and then to get the URLs of the new folders to put them in a cell.
I successfully created the folders based on the column "Employee ID" and replaced the values of the column "Employee ID" with the folder hyperlinks.
So I get the 3 folders created in Google Drive: 1,2 and 3, respectively. The problem is that my code repeats the same name and URL in every row in the Google Sheets setting the name and URL of the last folder that was created (the folder 3 in this case).
I would appreciate it if you could give me some help with this. This is my code:
function onEdit(e) {
if ([1, 2,].indexOf(e.range.columnStart) != 1) return;
createEmployeeFolder();
}
function createEmployeeFolder() {
var parent = DriveApp.getFolderById("1H0i69rE9WO0IAoxhnrFY2YKT_tD50fuX")
SpreadsheetApp.getActive().getSheetByName('Database').getRange('B3:B').getValues()
.forEach(function (r) {
if(r[0]) checkIfFolderExistElseCreate(parent, r[0]);
})
}
function checkIfFolderExistElseCreate(parent, folderName) {
var folder;
var idfolder;
var link;
try {
folder = parent.getFoldersByName(folderName).next();
} catch (e) {
folder = parent.createFolder(folderName);
idfolder = folder.getId();
link = folder.getUrl();
formula = '=hyperlink("' + link + '",' + folder + ')';
SpreadsheetApp.getActive().getSheetByName('Database').getRange('B3:B').setFormula(formula);
}
}
Ok I changed my code for this. I can create the folders but the problem is with the SetFormula I can't make it work in order to read every row to replace the ID with the URL. It just take the last ID and then it repeats the same ID in all the rows from the range. Please some help! :P
function createEmployeeFolder() {
var parent = DriveApp.getFolderById("1H0i69rE9WO0IAoxhnrFY2YKT_tD50fuX")
SpreadsheetApp.getActive().getSheetByName('Database').getRange('B3:B').getValues()
.forEach(function (r) {
if(r[0]) createFolder(r[0]);
})
}
function createFolder(folderName) {
var parent = DriveApp.getFolderById("1H0i69rE9WO0IAoxhnrFY2YKT_tD50fuX");
var projectFolder;
if (parent.getFoldersByName(folderName).hasNext()) {
// folder already exists
Folder = parent.getFoldersByName(folderName).next();
} else {
Folder = parent.createFolder(folderName);
}
var id = Folder.getId();
var link = Folder.getUrl();
var formula = '=hyperlink("' + link + '",' + Folder + ')';
SpreadsheetApp.getActive().getSheetByName('Database').getRange('B3:B').setFormula(formula);
return formula;
}
I have a function that does this for me. It takes a userName and returns a formula for a link to the folder that either existed or was created. This link can be put into a cell using setFormula():
function createUserFolder(userName) {
var parent = DriveApp.getFolderById("1H0i69rE9WO0IAoxhnrFY2YKT_tD50fuX");
if (parent.getFoldersByName(userName).hasNext()) {
// folder already exists
userFolder = parent.getFoldersByName(userName).next();
} else {
userFolder = parent.createFolder(userName);
}
var id = userFolder.getId();
var formula = '=HYPERLINK("https://drive.google.com/open?id=' + id + '","Files")'
return formula;
}
Please note however that you should rethink your code quite dramatically. onEdit is called for every single change in the spreadsheet which is very wasteful. Iterating through every single cell in column B is very wasteful. Using the catch clause in a try to do your critical code is a bad idea.
I would add a dialog or other ui to the sheet for triggering the creation of the folder rather than monitoring the sheet continually.
Solution:
The following script creates a folder in drive and a link with the name of the folder within a cell automatically.
function createAndHyperlink() {
var ss, sh, parent, parent2, r, v, thisCell, folder
ss = SpreadsheetApp.getActive()
sh = ss.getSheetByName('INSERTTHENAMEOFYOURSHEETHERE')
parent = DriveApp.getFileById(ss.getId()).getParents().next();
parent2 = DriveApp.getFolderById("INSERT-YOURGOOGLEDRIVEFOLDERIDEHERE")
r = sh.getRange('B3:B')
v = r.getValues()
for (var i = 0, l = v.length; i < l; i++) {
thisCell = sh.getRange(i + 3, 2)
if (v[i][0] && !thisCell.getFormula()) {
folder = parent2.createFolder(v[i][0]);
thisCell.setFormula('=HYPERLINK("' + folder.getUrl() + '"; "' + v[i][0] + '")');
}
}
}
Your issue stems from incomplete refactoring - you split the task of getting the folder id into a separate method, but also split the task of setting the hyperlink into that Range-unaware code.
The solution to repeating the last value across all other rows is to fix your refactoring - setting the formulas should be done where the range to write to is known.
function createEmployeeFolder() {
const parent = DriveApp.getFolderById("some id");
const sheet = SpreadsheetApp.getActive().getSheetByName('Database');
const range = sheet.getRange(3, 2, sheet.getLastRow() - 2);
range.getValues().forEach(function (row, index) {
// Consider checking if this r & c has a formula in the equal size array from getFormulas()
if(row[0]) {
var newLink = getLinkForFolderName_(parent, row[0]);
// Use the current array index to write this formula in only the correct cell.
range.offset(index, 0, 1).setFormula(
"=hyperlink(\"" + newLink + "\", \"" + row[0] + "\")";
);
}
});
}
function getLinkForFolderName_(root, name) {
var folder;
const search = root.getFoldersByName(name);
if (search.hasNext()) {
folder = search.next();
if (search.hasNext())
console.warn("Multiple folders named '" + name + "' in root folder '" + root.getName() + "'");
}
else
folder = root.createFolder(name);
return folder.getUrl();
}
You should do away with the onEdit simple trigger binding since simple triggers cannot create folders (you need authorization for that) - just change the name and install the on edit trigger for the new name. Another option is to use a menu option that calls this function. However, one (possible) benefit of using an installed trigger is that all folders will be owned by the same account.

Export Google Sheet as TXT file (Fixed Width)

I am trying to replicate some functionality from a Excel document with some macros in a Google Spreadsheet. The end goal is to export a fixed-width txt file. Unfortunately, the vendor cannot use a CSV file. So, does anybody know of a way to generate a fixed-width TXT file using Google Scripts?
Just in case anybody else comes looking. I used a few sources and came up with this:
function saveAsFixedWidthTxt() {
// get Spreadsheet Name
var fileName = SpreadsheetApp.getActiveSpreadsheet().getName();
//var fileName = SpreadsheetApp.getActiveSheet().getSheetName();
// get Directory the Spreadsheet is stored in.
var dirName = DocsList.getFileById(SpreadsheetApp.getActive().getId()).getParents()[0].getName();
// Add the ".txt" extension to the file name
fileName = fileName + ".txt";
// Convert the range data to fixed width format
var txtFile = convertRangeToTxtFile_(fileName);
// Delete existing file
deleteDocByName(fileName);
// Create a file in the Docs List with the given name and the data
DocsList.getFolder(dirName).createFile(fileName, txtFile);
}
function convertRangeToTxtFile_(txtFileName) {
try {
var txtFile = undefined;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var rows = sheet.getDataRange();
var data = rows.getValues();
// Loop through the data in the range and build a string with the data
if (data.length > 1) {
var txt = "";
for (var row = 2; row < data.length; row++) {
for (var j=6; j<=10; j++){
if(data[row][j] != ''){
// Employee ID
var empID = "" + data[row][3];
txt += empID;
//Fill to 6 characters
for (i=empID.length; i<6; i++){
txt += " ";
}
// Name
var fullName = data[row][5] + " " + data[row][4]
txt += fullName;
//Fill to 43 characters
for (i=fullName.length; i<44; i++){
txt += " ";
}
// etc, etc to build out your line
txt += "\r\n";
}
}
}
txtFile = txt;
}
return txtFile;
}
catch(err) {
Logger.log(err);
Browser.msgBox(err);
}
}
function deleteDocByName(fileName){
var docs=DocsList.find(fileName)
for(n=0;n<docs.length;++n){
if(docs[n].getName() == fileName){
var ID = docs[n].getId()
DocsList.getFileById(ID).setTrashed(true)
}
}
}
This will work:
export the sheet as a xlsx, then open that file in Excel.
Export that file as Space Delimited Text (.prn, for some reason).
Change the file extension on the resultant file to .txt.
This will result in a file like this:
Column Another Column 3
Val 1 2 $ 5.00
Or do you need to get a .txt file directly out of Google Apps Script?
Edit since a direct .txt download is necessary.
Here's how you could design a script to do that:
Find a way to convert sheet.getColumnWidth(n) to a number of spaces. You may find that there are 5 pixels to 1 character, or whatever the ratio
Run getColumnWidth() for each column to find the width you need for each column. Alternatively, find the length longest string in each cell.
Go through each cell and add it to a large string you begin building. As you add it, add the number of spaces necessary to match the value converted from getColumnWidth(). At the end of each row, append \n to represent a new line.
Once the output string is complete, you could then use the Content Service to download the text like so:
ContentService.createTextOutput(youBigString).downloadAsFile('filename.txt')
This would involve deploying your script as a web app, which could work well - you'd go to a URL and the page would trigger a download of the fixed-width file.
In my case, DocList use returns ReferenceError.#MSCF your lines 7 & 27 :
var dirName = DocsList.getFileById(SpreadsheetApp.getActive().getId()).getParents()[0].getName();
DocsList.getFolder(dirName).createFile(fileName, txtFile);
become
var dirName = DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).getParents().next().getId();
DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).getParents().next().createFile(fileName, txtFile);