Google docs to google sheets script - google-sheets
I’m using Google Sheets to conduct some text analysis. I would like to automate the process by linking a spreadsheet to a Google doc file on my GDrive to extract text directly. It doesn’t have to structured/formatted. It just has to be a plain text. Does it take a comprehensive scripting or the task of copying text is simple?
I searched the web but couldn’t find one.
To help you start, here is an SO thread.
Same scenario: Get data from the Google Sheets and copy it to the Google Docs.
A reference from Open Source Hacker: Script for generating Google documents from Google spreadsheet data source.
Here is the code provided in the article. You can modify it by yourself depending on your use.
/**
* Generate Google Docs based on a template document and data incoming from a Google Spreadsheet
*
* License: MIT
*
* Copyright 2013 Mikko Ohtamaa, http://opensourcehacker.com
*/
// Row number from where to fill in the data (starts as 1 = first row)
var CUSTOMER_ID = 1;
// Google Doc id from the document template
// (Get ids from the URL)
var SOURCE_TEMPLATE = "xxx";
// In which spreadsheet we have all the customer data
var CUSTOMER_SPREADSHEET = "yyy";
// In which Google Drive we toss the target documents
var TARGET_FOLDER = "zzz";
/**
* Return spreadsheet row content as JS array.
*
* Note: We assume the row ends when we encounter
* the first empty cell. This might not be
* sometimes the desired behavior.
*
* Rows start at 1, not zero based!!! 🙁
*
*/
function getRowAsArray(sheet, row) {
var dataRange = sheet.getRange(row, 1, 1, 99);
var data = dataRange.getValues();
var columns = [];
for (i in data) {
var row = data[i];
Logger.log("Got row", row);
for(var l=0; l<99; l++) {
var col = row[l];
// First empty column interrupts
if(!col) {
break;
}
columns.push(col);
}
}
return columns;
}
/**
* Duplicates a Google Apps doc
*
* #return a new document with a given name from the orignal
*/
function createDuplicateDocument(sourceId, name) {
var source = DocsList.getFileById(sourceId);
var newFile = source.makeCopy(name);
var targetFolder = DocsList.getFolderById(TARGET_FOLDER);
newFile.addToFolder(targetFolder);
return DocumentApp.openById(newFile.getId());
}
/**
* Search a paragraph in the document and replaces it with the generated text
*/
function replaceParagraph(doc, keyword, newText) {
var ps = doc.getParagraphs();
for(var i=0; i<ps.length; i++) {
var p = ps[i];
var text = p.getText();
if(text.indexOf(keyword) >= 0) {
p.setText(newText);
p.setBold(false);
}
}
}
/**
* Script entry point
*/
function generateCustomerContract() {
var data = SpreadsheetApp.openById(CUSTOMER_SPREADSHEET);
// XXX: Cannot be accessed when run in the script editor?
// WHYYYYYYYYY? Asking one number, too complex?
//var CUSTOMER_ID = Browser.inputBox("Enter customer number in the spreadsheet", Browser.Buttons.OK_CANCEL);
if(!CUSTOMER_ID) {
return;
}
// Fetch variable names
// they are column names in the spreadsheet
var sheet = data.getSheets()[0];
var columns = getRowAsArray(sheet, 1);
Logger.log("Processing columns:" + columns);
var customerData = getRowAsArray(sheet, CUSTOMER_ID);
Logger.log("Processing data:" + customerData);
// Assume first column holds the name of the customer
var customerName = customerData[0];
var target = createDuplicateDocument(SOURCE_TEMPLATE, customerName + " agreement");
Logger.log("Created new document:" + target.getId());
for(var i=0; i<columns.length; i++) {
var key = columns[i] + ":";
// We don't replace the whole text, but leave the template text as a label
var text = customerData[i] || ""; // No Javascript undefined
var value = key + " " + text;
replaceParagraph(target, key, value);
}
}
Related
Arduino google sheet integration
I'm doing a project with Arduino, I'm trying to post variable's data into google sheet integration but the code doesn't work. I tried to correct it, but it doesn't post anyway....this is the code. The error was ss.sheet.getSheetByName it's not a function I took the code from Arduino IoT Cloud Google sheet Integration function myFunction() { // get sheet named RawData var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dati"); var MAX_ROWS = 1440; // max number of data rows to display // 3600s / cloud_int(30s) * num_ore(12h) var HEADER_ROW = 1; // row index of header var TIMESTAMP_COL = 1; // column index of the timestamp column function doPost(e) { var cloudData = JSON.parse(e.postData.contents); // this is a json object containing all info coming from IoT Cloud //var webhook_id = cloudData.webhook_id; // really not using these three //var device_id = cloudData.device_id; //var thing_id = cloudData.thing_id; var values = cloudData.values; // this is an array of json objects // store names and values from the values array // just for simplicity var incLength = values.length; var incNames = []; var incValues = []; for (var i = 0; i < incLength; i++) { incNames[i] = values[i].name; incValues[i] = values[i].value; } // read timestamp of incoming message var timestamp = values[0].updated_at; // format: yyyy-MM-ddTHH:mm:ss.mmmZ var date = new Date(Date.parse(timestamp)); /* This if statement is due to the fact that duplicate messages arrive from the cloud! If that occurs, the timestamp is not read correctly and date variable gets compromised. Hence, execute the rest of the script if the year of the date is well defined and it is greater then 2018 (or any other year before) */ if (date.getYear() > 2018) { // discard all messages that arrive 'late' if (sheet.getRange(HEADER_ROW+1, 1).getValue() != '') { // for the first time app is run var now = new Date(); // now var COMM_TIME = 5; // rough overestimate of communication time between cloud and app if (now.getTime() - date.getTime() > COMM_TIME * 1000) { return; } } // this section write property names sheet.getRange(HEADER_ROW, 1).setValue('timestamp'); for (var i = 0; i < incLength; i++) { var lastCol = sheet.getLastColumn(); // at the very beginning this should return 1 // second cycle -> it is 2 if (lastCol == 1) { sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]); } else { // check if the name is already in header var found = 0; for (var col = 2; col <= lastCol; col++) { if (sheet.getRange(HEADER_ROW, col).getValue() == incNames[i]) { found = 1; break; } } if (found == 0) { sheet.getRange(HEADER_ROW, lastCol+1).setValue(incNames[i]); } } } // redefine last column and last row since new names could have been added var lastCol = sheet.getLastColumn(); var lastRow = sheet.getLastRow(); // delete last row to maintain constant the total number of rows if (lastRow > MAX_ROWS + HEADER_ROW - 1) { sheet.deleteRow(lastRow); } // insert new row after deleting the last one sheet.insertRowAfter(HEADER_ROW); // reset style of the new row, otherwise it will inherit the style of the header row var range = sheet.getRange('A2:Z2'); //range.setBackground('#ffffff'); range.setFontColor('#000000'); range.setFontSize(10); range.setFontWeight('normal'); // write the timestamp sheet.getRange(HEADER_ROW+1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss"); // write values in the respective columns for (var col = 1+TIMESTAMP_COL; col <= lastCol; col++) { // first copy previous values // this is to avoid empty cells if not all properties are updated at the same time sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue()); for (var i = 0; i < incLength; i++) { var currentName = sheet.getRange(HEADER_ROW, col).getValue(); if (currentName == incNames[i]) { // turn boolean values into 0/1, otherwise google sheets interprets them as labels in the graph if (incValues[i] == true) { incValues[i] = 1; } else if (incValues[i] == false) { incValues[i] = 0; } sheet.getRange(HEADER_ROW+1, col).setValue(incValues[i]); } } } } // end if (date.getYear() > 2018) } }
You are trying to push data directly to the Google sheet on the cloud but it's URL uses https for security reasons. The encryption for https is rather complex and SSL crypto is required. Arduino hardware is normally not fast enough to do SSL. You need more powerful hardware like Raspberry Pi to write directly to the Google Cloud. However, you can use PushingBox, a cloud that can send notifications based on API calls, to do the hard work for you if you insist on using Arduino ( any variant) in your IOT project. Here you send your data to Pushingbox, it uses http URL, and it will in turn the data to Google sheet.
How to share filtered data with same filter view in google sheets?
I am using google spreadsheet and this is my code to import data and save my filter on it. function importCSVFromWeb() { // Provide the full URL of the CSV file. var csvUrl = "https://covid19.who.int/WHO-COVID-19-global-table-data.csv"; var csvContent = UrlFetchApp.fetch(csvUrl).getContentText(); var csvData = Utilities.parseCsv(csvContent); var sheet = SpreadsheetApp.getActiveSheet(); // --- I added below script. var filter = sheet.getFilter(); var range = filter.getRange(); var criteria = []; var start = range.getColumn(); var end = start + range.getNumColumns() - 1; for (var i = start; i <= end; i++) { var criterion = filter.getColumnFilterCriteria(i); if (criterion) criteria.push({col: i, c: criterion.copy()}); } filter.remove(); // --- sheet.getRange(1, 1, csvData.length, csvData[0].length).setValues(csvData); // And, I added below script. criteria.forEach(({col, c}) => range.createFilter().setColumnFilterCriteria(col, c)); } My issue is: How can i share the spreadsheet with my filter applied on it to use it ? In my case, I want to use another visualization tool on only filtered data
Google Sheets - Unmerge cells and fill down
I need to take a sheet maintained by someone else and do the following (so that I can export to a csv): unmerge all cells fill values down merged cells are in multiple columns, so I need to iterate over a range It's too much to do it by hand, and it will need done periodically. My javascript and google sheets object model knowledge approximate zero, but I know it's possible because I could do it in VBA. I searched but can only find programmatic answers for VBA/Excel. How can I do this efficiently in Google Sheets?
You can use the breakapart() class to do this. I am assuming that the merged range is not static and has multiple occurrences. This script will unmerge all merged ranges in the active sheet. function myFunction() { var sheet = SpreadsheetApp.getActiveSheet(); sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).breakApart(); }
Adapted from #lreeder's answer The following breaks and fill blank with above on the selected range: function onOpen() { var ui = SpreadsheetApp.getUi(); // Or DocumentApp or FormApp. ui.createMenu('BreakAndFill') .addItem('Break Fill Blank Cells', 'menuItem1') .addToUi(); } function menuItem1() { BreakandfillBlankWithAbove() } //Breaks range //Iterates over the range from top to bottom //and left to right, and fills blank cells //with the value right above them. function BreakandfillBlankWithAbove() { var sheet = SpreadsheetApp.getActiveSheet(); var range = sheet.getActiveRange(); Logger.log('Range height is: ' + range.getHeight()); var values = range.getValues(); range.breakApart(); var width = values[0].length; var height = values.length; Logger.log('Width is ' + width); Logger.log('Height is ' + height); for (var i = 0; i < width; i++) { var lastVal = ''; for(var j = 0; j < height; j++) { var currValue = values[j][i]; var dataType = typeof(currValue); //Empty string check returns true if dataType //is a number, and value is zero. In that case //we don't want to overwrite the zero, so only //check emptyString for string types. if(currValue === undefined || (dataType === 'string' && currValue == '') ) { //getCell parameters are relative to the current range //and ones based var cell = range.getCell(j+1, i+1); cell.setValue(lastVal); } else { lastVal = currValue; } } } SpreadsheetApp.flush(); }
For all e-mail entries that do not have a firstname, replace with 'Gamer'
I manage a large email list for my gaming society. In column A, I have the e-mails, and in column B, I have the usernames. I populate column B with a formula that extracts the name of the user from their e-mail address, which is often in the firstname.lastname#email.com form. Column B therefore returns "Firstname" after I run the formula if the user's email is in the firstname.lastname#email.com format. Sometimes, however, the emails have just the initial of the first name (f.lastname#email.com) and, in these case, I want to have Column B return the word 'Gamer' rather than, for example, the first letter of the user's email. Here is the script I use at the moment, which current deletes all rows with four or more numbers: function removeNumbers() { var sheet = SpreadsheetApp.getActiveSheet(); var rows = sheet.getDataRange(); var numRows = rows.getNumRows(); var values = rows.getValues(); var rowsDeleted = 0; for (var i = 0; i <= numRows - 1; i++) { var row = values[i]; if (row[0].toLowerCase().indexOf("robot") > -1) { sheet.deleteRow((parseInt(i)+1) - rowsDeleted); rowsDeleted++; }
On your shared spreadsheet, use this function firstName() { var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet() var lr=sheet.getLastRow() var val = sheet.getRange(2,1,lr-1,2).getValues();//get the col A email addrs var newVal=[]//new array for before fitst period for (var i = 0; i <= val.length-1; i++) { var str=val[i][0].split(".")//split email at period var len=str[0].length // determine lenght of number string of first split if(val[i][1]=="inactive"){ newVal.push(["inactive"]) continue } if(len<=1){//if 1 or less def="Gamer" newVal.push([def]) //put Gamer in new array } else{ newVal.push([toTitleCase(str[0])]) //keep first name }} sheet.getRange(2, 2, newVal.length, 1).setValues(newVal)//set new values in col B } function toTitleCase(str) { return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0) .toUpperCase() + txt.substr(1) .toLowerCase(); }); } This well also run on the active sheet.
Google Sheets Lookup Tool Assistance
I've taken it upon myself the learn how to "excel". That made a little more sense in my head, but hey. I have built a "lookup tool" which can be viewed here. The "Dishwasher search" is working as intended. You can search by noise level or decor panel height to create a list of suitable models then pull the data into an individual search by product code/model no. I decided to make it a little more difficult and created one for Ovens. The Layout is like this; Main search>Single Oven Database>Double Oven Database>Built-under oven database. My goal is to achieve the same search facilities as the "Dishwasher tool", however I have been unsure how to search (Vlookup) from different sources. I have tried creating a "Master DB" using the formula below; ={Importrange("1mY13e-75dBYfKgkjV8dFFFEvxC838nGNxPrUdusc0PA", "'Single Ovens'!$A:$F");Importrange("1mY13e-75dBYfKgkjV8dFFFEvxC838nGNxPrUdusc0PA", "'Double Ovens'!$A:$F");Importrange("1mY13e-75dBYfKgkjV8dFFFEvxC838nGNxPrUdusc0PA", "'Built-Under Ovens'!$A:$F")))} However, it only seems to pull data from the first range not the others unless I do it horizontaly rather than vertical, but horizontal wont work with my Vlookup formula (To my knowledge). The other method I have tried is using this code; =IF(AND($A$19="Single Oven",$A$4>0), Vlookup($A$4,'Single Ovens'!$B:$F,1,False),IF(AND($A$10="Double Oven",$A$4>0), Vlookup($A$4,'Double Ovens'!$B:$F,1,False),If(AND($A$10="Built-Under Oven",$A$4>0), Vlookup($A$4,'Built-Under Ovens'!$B:$F,1,False),IF($A$10="Single Oven",Vlookup($A$7,'Single Ovens'!$A:$F,2,False),IF($A$10="Double Oven", Vlookup($A$7,'Double Oven'!$A:$F,2,False),If($A$10="Built-Under Oven", Vlookup($A$7,'Built-Under Oven'!$A:$F,2,False))))))) The above code was inserted and was "meant" to Vlookup all 3 sheets and pull "product Code" through to populate Cell D4 on sheet 'Ovens'. Now, I'm a bit of a novice at this but I'm working to understand it all :) Any suggestions you guys can make or point me in the right direction? Edit - Sorry guys. It was rude not to post my solution. I have changed my Importrange function to =Importrange("XYZ",""'Single Ovens'!$A2:$F200") and repeated this 3 times with a gap of 200 "rows" between each one. Not a solution, but a viable alternative. My friend is currently working on a script for me to achieve this. The Vlookup formula no longer needs to be as complex thanks to the importrange formula simplifying matters.
So after discussing the problem with McSheehy, and the problem is actually this. How to get data from ONE spreadsheet, multiple sheets of my choice. and write To MANY spreadsheets, Multiple sheets within those spreadsheets, of my choice. Once that data is in the right place, the current formulas should work or can be adapted easily. I came up with a script solution, The user creates a settings sheet in the source sheet. In A2 downwards, Target spreadsheet keys, B2 downwards, Source sheet names you wish to include from current sheet. C2 downwards is the target SHEET names, if you wanted to write data to more than one sheet. Bits of code are annotated to help explain McSheehy's questions on how it works. If anyone has any improvements to suggest, and I'm sure there are some, particular the headers bit. (its not needed, but my clearContent/clearConents line kept flipping out), I'm all ears. Thanks function getOvenDataV5(){ var settingsSheetName = "monkey_settings"; /* DO NOT EDIT BELOW THIS LINE */ var ss = SpreadsheetApp.getActiveSpreadsheet(); var settings = ss.getSheetByName(settingsSheetName); // this bit has been edited, note the getValues, not getValue, as we want the whole column now not just a single cell. var targetSheetsValues = settings.getRange("C2:C").getValues(); // this gets the target sheet names from the settings sheet var targetSheets = []; // And array added to throw target sheet names into, as there is more than one. // the reason we use arrays and loops (later on), is because the script has no idea how much data to expect. // so we go through whatever it's grabbed, the stuff it thinks is data, but we check it later on. // only a simple check. Our check is that it cannot be blank. "" // then stuff it in an array, a handy thing to store data, for use later on. var sSheets = settings.getRange("B2:B").getValues(); var sourceSheets = []; // new loop below to get the target sheets names. We'll use this in the write bit later. for(var i = 0; i < targetSheetsValues.length;i++){ if(targetSheetsValues[i][0]!=""){ targetSheets.push(targetSheetsValues[i]); } } for(var i = 0; i < sSheets.length;i++){ if(sSheets[i][0]!=""){ sourceSheets.push(sSheets[i]); } } var dKeys = settings.getRange("A2:A").getValues(); var sKeys = []; for(var i = 0; i < dKeys.length;i++){ if(dKeys[i][0]!=""){ sKeys.push(dKeys[i]); } } var data = []; for (var i = 0; i < sourceSheets.length;i++){ var values = ss.getSheetByName(sourceSheets[i]).getDataRange().getValues(); for (var x = 1;x < values.length; x++){ if(values[x][0]!= ""){ data.push(values[x]); } } } // Below is an array of your column headers, the script was being annoying when clearing sheet data, so decided to clear the whole damn sheet // then write the headers via here instead var headers = [["Model No", "Product Code", "Brand", "Model No", "kW", "Amp"]]; for (var i = 0; i< sKeys.length;i++){ var tss = SpreadsheetApp.openById(sKeys[i]); for(var x = 0; x < targetSheets.length;x++){ // this loop, within the keys loop, goes through the target sheets array var target = tss.getSheetByName(targetSheets[x]); // this loads the target sheet, one by one var range = target.getRange(2,1, data.length, data[0].length); // this gets the cells to write to target.clearContents(); // clear the sheet before writing data target.getRange("A1:F1").setValues(headers); // write the headers to a1:F1 in target sheet range.setValues(data); //write the data } } }
I dont know if they will be of any value but here are two things that i wrote in script. The follow is a vlookup script to set or return to memory with hints of query ish abilites. //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` //--//Dependent on isEmpty_() // Script Look-up /* Benefit of this script is: -That google sheets will not continually do lookups on data that is not changing with using this function as it is set with hard values until script is kicked off again. -Unlike Vlookup you can have it look at for reference data at any Column in the row. Does not have to be in the first column for it to work like Vlookup. -You can return the Lookup to Memory for further processing by other functions Useage: Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"Sheet1!I1","n","y"); //or Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"return","n","n"); //or Lookup_(Sheetinfo,"Sheet1!A:B",0,[0,1],"return","n","n"); //or Lookup_(Sheetinfo,"Sheet1!A:B",1,[0],"return","y","n"); //or Lookup_("cat","Sheet1!A:G",4,[0],"Database!A1","y","y"); -Loads all Locations numbers from J2:J into a variable --looks for Location Numbers in Column 0 of Referance sheet and range eg "Data!A:G" ---Returns results to Column 3 of Target Sheet and range eg "test!A1" or "1,1" */ function Lookup_(Search_Key,RefSheetRange,SearchKey_Ref_IndexOffSet,IndexOffSetForReturn,SetSheetRange,ReturnMultiResults,Add_Note) { var RefSheetRange = RefSheetRange.split("!"); var Ref_Sheet = RefSheetRange[0]; var Ref_Range = RefSheetRange[1]; if(!/return/i.test(SetSheetRange)) { var SetSheetRange = SetSheetRange.split("!"); var Set_Sheet = SetSheetRange[0]; var Set_Range = SetSheetRange[1]; var RowVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getRow(); var ColVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getColumn(); } var twoDimensionalArray = []; var data = SpreadsheetApp.getActive().getSheetByName(Ref_Sheet).getRange(Ref_Range).getValues(); //Syncs sheet by name and range into var for (var i = 0, Il=Search_Key.length; i<Il; i++) // i = number of rows to index and search { var Sending = []; //Making a Blank Array var newArray = []; //Making a Blank Array var Found =""; for (var nn=0, NNL=data.length; nn<NNL; nn++) //nn = will be the number of row that the data is found at { if(Found==1 && ReturnMultiResults.toUpperCase() == 'N') //if statement for found if found = 1 it will to stop all other logic in nn loop from running { break; //Breaking nn loop once found } if (data[nn][SearchKey_Ref_IndexOffSet]==Search_Key[i]) //if statement is triggered when the search_key is found. { var newArray = []; for (var cc=0, CCL=IndexOffSetForReturn.length; cc<CCL; cc++) //cc = numbers of columns to referance { var iosr = IndexOffSetForReturn[cc]; //Loading the value of current cc var Sending = data[nn][iosr]; //Loading data of Level nn offset by value of cc if(isEmpty_(Sending)) //if statement for if one of the returned Column level cells are blank { var Sending = "#N/A"; //Sets #N/A on all column levels that are blank } if (CCL>1) //if statement for multi-Column returns { newArray.push(Sending); if(CCL-1 == cc) //if statement for pulling all columns into larger array { twoDimensionalArray.push(newArray); var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop break; //Breaking cc loop once found } } else if (CCL<=1) //if statement for single-Column returns { twoDimensionalArray.push(Sending); var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop break; //Breaking cc loop once found } } } if(NNL-1==nn && isEmpty_(Sending)) //following if statement is for if the current item in lookup array is not found. Nessessary for data structure. { for(var na=0,NAL=IndexOffSetForReturn.length;na<NAL;na++) //looping for the number of columns to place "#N/A" in to preserve data structure { if (NAL<=1) //checks to see if it's a single column return { var Sending = "#N/A"; twoDimensionalArray.push(Sending); } else if (NAL>1) //checks to see if it's a Multi column return { var Sending = "#N/A"; newArray.push(Sending); } } if (NAL>1) //checks to see if it's a Multi column return { twoDimensionalArray.push(newArray); } } } } if (CCL<=1) //checks to see if it's a single column return for running setValue { var singleArrayForm = []; for (var l = 0,lL=twoDimensionalArray.length; l<lL; l++) //Builds 2d Looping-Array to allow choosing of columns at a future point { singleArrayForm.push([twoDimensionalArray[l]]); } if(!/return/i.test(SetSheetRange)) { SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,singleArrayForm.length,singleArrayForm[0].length).setValues(singleArrayForm); SpreadsheetApp.flush(); if(/y/i.test(Add_Note)) { SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: " + Ref_Sheet + "!" + Ref_Range); } } else { return singleArrayForm } } if (CCL>1) //checks to see if it's a multi column return for running setValues { if(!/return/i.test(SetSheetRange)) { SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,twoDimensionalArray.length,twoDimensionalArray[0].length).setValues(twoDimensionalArray); SpreadsheetApp.flush(); if(/y/i.test(Add_Note)) { SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: " + Ref_Sheet + "!" + Ref_Range); } } else { return twoDimensionalArray } } } //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` //--//Dependent on isEmpty_() //Find Last Row on Database function getFirstEmptyRowUsingArray_(sheetname) { var data = SpreadsheetApp.getActive().getSheetByName(sheetname).getDataRange().getValues(); for(var n = data.length ; n>0 ; n--) { if(isEmpty_(data[n])==false) { n++; break; } } n++; return (n); } //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` The Following is a ImportRange script because I hate how =importrange() requires you to "request access" on every new formula. If that importrange is embedded you have to take it out and request access and re-embed it. Also a lot of times the formula just breaks. so: //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` //Script Based ImportRange //Example importRange_('0AodPsggrbsh1dDNH............','Main4NS','A:G','Common','C7','y') //Explanation importRange_('Importing Spreadsheet Key or URL','Importing Spreadsheet Tab Name','Importing Spreadsheet Tab's Range','Destination Spreadsheet Tab Name','Destination Spreadsheet Tab's placement','Will add note to the first cell of import') function importRange_(Source_Key,Source_Sheet,Source_Range,Set_Sheet,Set_Pos,Add_Note) { var SourceTypeCheck = Source_Key.indexOf("https://"); if(SourceTypeCheck >= 0) { var Load = SpreadsheetApp.openByUrl(Source_Key).getSheetByName(Source_Sheet).getRange(Source_Range).getValues(); var Name = SpreadsheetApp.openByUrl(Source_Key).getName(); } if(SourceTypeCheck == -1) { var Load = SpreadsheetApp.openById(Source_Key).getSheetByName(Source_Sheet).getRange(Source_Range).getValues(); var Name = SpreadsheetApp.openById(Source_Key).getName(); } var RowVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Pos).getRow(); var ColVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Pos).getColumn(); if(Add_Note.toUpperCase() == 'Y') { SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("Import Script Updated On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a")+"\nSS Name: "+Name+"\nRange: "+Source_Sheet+"!"+Source_Range+"\nSS Key: "+ Source_Key); } SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,Load.length,Load[0].length).setValues(Load); SpreadsheetApp.flush(); SpreadsheetApp.getActiveSpreadsheet().toast('At: '+Set_Sheet+'!'+Set_Pos,'Import Completed:'); } //~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~` Same thing stands. If you have and ideas for improvement let me know. If you find a way to integrate it, cool beans, Post some code of how you used it. :)