please can anyone help with one problem in google spreadsheet?
After changing value in one concrete collumn in sheet "Venues", I would like to write log about name and time, when this value was changed. But I can't really realize , if I am working with spreadsheet "Venues" or some other. I am not very into class structure of google API for Spreadsheet. So can anyone help with it?
I need:
run eventhandler on event when value in appropriate column in appropriate sheet ("Venues") is changed
get value from collumn name from this sheet
get actual time
write name and time to another sheet called "status_history" to last row (like append)
My hard try to write something: (but that is really bad code)
function onEdit(event)
{
var sheet = event.source.getActiveSheet();
var cell = sheet.getActiveCell();
var cellR = cell.getRow();
var cellC = cell.getColumn();
var cellValue = cell.getValue();
var cellCName = cell.getColumn()-1; //column with names
var name = sheet.getRange(cellR, cellCName).getValue();//get name
var active_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
if(sheet.getName() == "Venues"){
if(cellC == 5 /* if correct collumn was changed */){
var output_sheet = active_spreadsheet.getSheetByName("status_history");
var lastRow = output_sheet.getLastRow();
var lastRange = output_sheet.getRange(lastRow, 1)
//HERE: write value: name
var lastRow = output_sheet.getLastRow();
var lastRange = output_sheet.getRange(lastRow, 2)
//HERE: write value: time
}
}
}
You were getting there. Just a couple of tweaks needed.
With onEdit functions, you need to keep things fast, since they get invoked so often.
Rely on the event information as much as you can, avoiding calls to Google Apps services.
If you must use a service, do it only when you absolutely need to - for example, wait until you are past the if statements that tell whether you are in a cell you want to log before calling SpreadsheetApp.getActiveSpreadsheet().
The API is rich, so look for functions that will let you reduce the number of system calls you make - see how appendRow() replaced multiple statements, for example.
Here's your function after a code inspection:
function onEdit(event) {
var sheet = event.range.getSheet();
if(sheet.getName() == "Venues"){
// correct sheet
var cell = event.range;
//var cellR = cell.getRow(); // not used at this time
var cellC = cell.getColumn();
var cellValue = event.value;
if (cellC == 5) {
// correct column
var name = cell.offset(0,-1).getValue(); // get name, 1 column to left
var time = new Date(); // timestamp
var active_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var output_sheet = active_spreadsheet.getSheetByName("status_history");
output_sheet.appendRow([name,time]);
}
}
}
You could make it more flexible and portable by using column names to test conditions. Take a look at Adam's answer here.
Related
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var clearContentRange = pasteSheet.getRange("A1:Z100");
clearContentRange.clearContent();
var source = copySheet.getRange("a1:f100");
var destination = pasteSheet.getRange("a1:f100");
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
var rows = pasteSheet.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[3] == '') {
var deleteRowNum = (parseInt(i)+1) - rowsDeleted
pasteSheet.deleteRow(deleteRowNum);
// temp_array[i] = i
rowsDeleted++;
}
}
pasteSheet.deleteColumn(2)
}
Hi,
I have written the following script to perform the following operations,
Copy data from My-Sheet-1 and paste to My-Sheet-2
Deletion of rows that corresponds to empty cells in column 3.
After that deletion of column 2
Rigthnow, the My-Sheet-1 contains only 60 rows and 20, the script is taking approximately 7 secs to complete. However in future the number of rows may extend to some 1000. Is there a way to optimize the above code, so that it takes less time to complete.
My observation is that, copy and pasting the data takes just milli secs. The major time consuming part are the operations, I am performing in the pasteSheet after pasting it. It may be helpful, if we can copy My-Sheet-1 to a temporary variable (copy everything including the formulas, format specifications, values, text etc..) and perform all operations in the temporary variable and then paste everything in the temporary variable to the desired target sheet. But, I don't know, how to copy everything in a sheet to a temporary variable, also, I am not sure, whether this will reduce the time or not. I would be glad, if I can get some help on this as well (i.e. copying everything in a sheet to a temporary variable, perfrom operations on the variables and then paste data in the variable to a new sheet)
Thank you
Edit - 1
Would like to add that, My-Sheet-1 contains mixed data (i.e. numerics, color formatted text, formulas in some cells etc)
Explanation:
deleteRow() takes some time per execution, so it's not recommended to use on hundreds of rows in a loop.
Simple answer would be:
Make a 2D array for Sheet1 using getValues().
Delete / filter out array elements depending if row2 is blank.
Use setValues() to write the filtered array into Sheet2.
Sample Code:
function dataManp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("My-Sheet-1");
var pasteSheet = ss.getSheetByName("My-Sheet-2");
var lr = copySheet.getLastRow();
var clearContentRange = pasteSheet.getRange(1,1,lr,26);
clearContentRange.clearContent();
var source = copySheet.getRange(1,1,lr,6);
var destination = pasteSheet.getRange(1,1,lr,6);
source.copyTo(destination, {formatOnly:true , contentsOnly:true});
source.copyTo(destination,SpreadsheetApp.CopyPasteType.PASTE_COLUMN_WIDTHS,false);
destination.clearContent();
var values = source.getValues();
var temp_array = [];
for (var i = 0; i < lr; i++) {
var rowValue = values[i];
if (rowValue[2] != '') {
temp_array.push(rowValue);
}
}
var newDest = pasteSheet.getRange(1,1,temp_array.length,6)
newDest.setValues(temp_array);
pasteSheet.deleteColumn(2);
}
One caveat is that you need to have the same format for all rows in a column.
Sample Input:
Sample Output:
I have searched a lot about this but no results so far.
My question is pretty simple, What are the possible ways to get a dropdown's(which i created with datavalidations) selected value without using onEdit() or any trigger in Google Script.
I know this might be a pretty simple or even dumb question, but I am a novice in google sheets and script.
Thanks for the help
Edit:
These are the images of my sheet, I want to get the dropdown's value from the current sheet and then get the count of the selected element's frequency in new sheet and hence fill the count in the count column in the current sheet again.
Code:
function onOpen(){
var ss = SpreadsheetApp;
var currSheet = ss.getActiveSpreadsheet().getActiveSheet();
ScriptApp.newTrigger('myEdit')
.forSpreadsheet(currSheet)
.onEdit()
.create();
}
function myEdit(e){
var activeCell = e.range;
var val = activeCell.getValue();
var wsName = activeCell.getSheet().getName();
var r = activeCell.getRow();
var c = activeCell.getColumn();
if(wsName ==="Math" && r > 1)
returnFun(e.value);
}
function returnFun(selectedDropdown){
var ss = SpreadsheetApp;
var currSheet = ss.getActiveSpreadsheet().getActiveSheet();
var oss = SpreadsheetApp.openById(" Some id");
var sheet = oss.getSheetByName(" Some Name ");
// Here I check the selectedDropdown with a column of oss Spreadsheet
// but the onEdit trigger keeps giving me error that I cant access that file
// this error only occurs when I use onEdit(), else the oss sheet works fine.
}
Explanation
Installable triggers inherit authorization from the process that created them. The onOpen() trigger you have is set to run on the current spreadsheet but at the same time it is creating an installable onEdit() trigger. This onEdit() trigger is later making use of another spreadsheet, for which you didn't authorize the permissions for.
Therefore, I suggest you create the onEdit() trigger by going to Current project's triggers -> Add trigger and choose the myEdit function for the trigger to run on. I also suggest you to skip the onOpen() trigger as you are only using it for creating the other trigger.
Snippet
function myEdit(e){
var activeCell = e.range;
var val = activeCell.getValue();
var wsName = activeCell.getSheet().getName();
var r = activeCell.getRow();
var c = activeCell.getColumn();
if (wsName === "Math" && r > 1)
returnFun(e.value);
}
function returnFun(selectedDropdown){
var ss = SpreadsheetApp;
var currSheet = ss.getActiveSpreadsheet().getActiveSheet();
var oss = SpreadsheetApp.openById(" Some id");
var sheet = oss.getSheetByName(" Some Name ");
// Here I check the selectedDropdown with a column of oss Spreadsheet
// but the onEdit trigger keeps giving me error that I cant access that file
// this error only occurs when I use onEdit(), else the oss sheet works fine.
}
Reference
Installable Triggers
I have this function which works but it gets all responses.
function setEditUrl(ss, createDateColumn)
{
var formURL = 'https://docs.google.com/forms/d/101bMiRw9TQaGbdDc4U_tLAD0QzicqejM9qXOEwJPQKU/viewform';
var urlColumn = createDateColumn-2;
var data = ss.getDataRange().getValues();
var form = FormApp.openByUrl(formURL);
for(var i = 2; i < data.length; i++)
{
if(data[i][0] != '' && data[i][urlColumn-1] == '')
{
var timestamp = data[i][0];
var formSubmitted = form.getResponses(timestamp);
if(formSubmitted.length < 1) continue;
var editResponseUrl = formSubmitted[0].getEditResponseUrl();
ss.getRange(i+1, urlColumn).setValue(editResponseUrl);
}//end of if
}//end of for
return;
}// This is the end of the setEditUrl function
As the spreadsheet gets larger I am concerned with performance lag so I want to streamline it and replace the function with one like the one below which just gets the editURL for the last response and only if the sheet cell is empty
function setGoogleFormURL(ss, lastRowInx, createDateColumn)
{
var urlColumn = createDateColumn-2;
if (ss.getRange(lastRowInx, urlColumn).getValue() == "") // so that subsequent edits to Google Form don't overwrite editResponseURL
{
var form = FormApp.openById('101bMiRw9TQaGbdDc4U_tLAD0QzicqejM9qXOEwJPQKU');
var formResponses = form.getResponses();
var lastResponseIndex = form.getResponses.length-1;
var lastResponse = formResponses[lastResponseIndex];
var editResponseUrl = lastResponse.getEditResponseUrl();
var createEditResponseUrl = ss.getRange(lastRowInx, urlColumn);
createEditResponseUrl.setValue(editResponseUrl);
}
else{} //do nothing
however this seems to break on the getEditResponseUrl. I am getting the following error TypeError: Cannot call method "getEditResponseUrl" of undefined. (line 100, file "Code").
I used #SandyGood 's answer to this post as a reference. I wonder though if her observation about the event trigger is why this is borking. This is the onFormSubmit function I am using to call this and other fucntions.
function onFormSubmit(e)
{
var ss = SpreadsheetApp.getActiveSheet();
var lastRowInx = ss.getLastRow(); // Get the row number of the last row with content
var createDateColumn = ss.getMaxColumns(); //CreateDateColumn is currently in AX (Column 50) which is the last/max column position
var createDate = setCreateDate(ss, lastRowInx, createDateColumn);
var trackingNumber = setTrackingNumber(ss, lastRowInx, createDateColumn);
//var editURL = setEditUrl(ss, createDateColumn);
var editResponseURL = setGoogleFormURL(ss, lastRowInx, createDateColumn);
}//This is the end of onFormSubmit
I also found a whole bunch of sources 234where they were looking use the URL to append to an email, were more complex than my use case, or were unanswered. I also found some solutions for getting the EditURL by binding the script to the form but since I want to store the value on the sheet it needs to be bound to the sheet rather than the form.
UPDATE:
Okay so I tried to bind my script to the form instead of the sheet which allowed me to see the URL but now I have the problem in reverse where the form can't find the spreadsheet methods like .getMaxColumns TypeError: Cannot find function getMaxColumns in object Spreadsheet. (line 40, file "Code") AND .getActiveRange Cannot find method getActiveRange(number). (line 48, file "Code").
Here is the code on the form side
function onFormSubmit(e)
{
var form = FormApp.getActiveForm();
var activeFormUrl = form.getEditUrl();
var ss = SpreadsheetApp.openById(form.getDestinationId());
var createDateColumn = ss.getMaxColumns(); //CreateDateColumn is currently in AY (Column 51) which is the last/max column position
var urlColumn = createDateColumn-1; //urlColumn is currently in AX (Column 50) Calculating using it's relative position to createDateColumn Position
Logger.log(activeFormUrl, createDateColumn, urlColumn);
var checkLog1 = Logger.getLog();
Logger.clear();
if (ss.getActiveRange(urlColumn).getValue() == "") // so that subsequent edits to Google Form don't overwrite editResponseURL
{
var editResponseURL = setGoogleFormEditUrl(ss, createDateColumn, activeFormUrl);
var createEditResponseUrl = ss.getActiveRange(urlColumn);
createEditResponseUrl.setValue(activeFormUrl);
}
else
{
if (ss.getActiveRange(urlColumn).getValue() != activeFormUrl)
{
Logger.log("Something went wrong - URL doesn't match")
Logger.log(ss.getActiveRange(urlColumn).getValue());
var checkLog2 = Logger.getLog();
}
else {}//do nothing
}
}//This is the end of the onFormSubmit function
So I am wondering how I can pass a variable between the form and the sheet. Can I somehow read the form log programmically from the sheet? Can I append the value to the form response array (This would mean a few other edits to the referenced columns but could work). Thoughts #Gerneio , #SandyGood , Anyone else?
UPDATE 2:
There seemed to be a conflict with using both the methods from the FormApp and the SpreadsheetApp within the same function.
The solution that worked for me was to modularize the spreadsheet functions out (except the getActiveSheet) and to leave the getEditResponseURL method within the onFormSubmit Function.
The code snippet can be found posted here.
I'd suggest trying to use the onFormSubmit(e) on the form side.
function onFormSubmit(e)
{
var form = e.source;
var response = e.response;
var sheet = SpreadsheetApp.openById(form.getDestinationId());
var editUrl = response.getEditResponseUrl();
Logger.log(editUrl); // check the logger to see what results you are getting now
// Then do whatever operations you need to do...
}
Update:
I'm not so sure why you are having so many problems with this, but I can tell you for sure that it can be done from either side, the Form or Spreadsheet. I just put together a working example with code written on the Spreadsheet side, none what-so-ever on the Form side. Check it out:
function onFormSubmit(e)
{
var rng = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var fUrl = ss.getFormUrl();
var f = FormApp.openByUrl(fUrl);
var rs = f.getResponses();
var r = rs[rs.length - 1]; // Get last response made
var c = getCellRngByCol(rng, 'Edit Response URL');
c.setValue(r.getEditResponseUrl());
}
// Specific for a form submit trigger
// Pass e.range and the name of the column
// to return a single cell
function getCellRngByCol(rng, col)
{
var aRng = SpreadsheetApp.getActiveSheet().getDataRange();
var hRng = aRng.offset(0, 0, 1, aRng.getNumColumns()).getValues();
var colIndex = hRng[0].indexOf(col);
return SpreadsheetApp.getActiveSheet().getRange(rng.getRow(), colIndex + 1);
}
There were a few small hiccups that I ran into. Firstly, make sure to setup the trigger accordingly. I highly recommend setting up immediate notifications of failures. Secondly, even though the function will rely on the event that is passed, manually run the onFormSubmit(e) method at least once before submitting a form. It will check to see if your script needs any authorization and will request if needed. I'd also recommend that you open up a new form, link a fresh new spreadsheet, and test this code to make sure it works. Then mold the above code to fit your needs.
If you can't get it, then I'll share a working example.
There seemed to be a conflict with using both the methods from the FormApp and the SpreadsheetApp within the same function.
The solution that worked for me was to modularize the spreadsheet functions out (except the getActiveSheet) and to leave the getEditResponseURL method within the onFormSubmit Function.
The code snippet can be found posted here.
Sheet 2 has all the items and their statuses, while Sheet 1 has only some of the items from Sheet 2. I want to be able to see every time an item mentioned on Sheet 1 is listed as having a status update, i.e. e date, on Sheet 2.
Here's what I have so far, but having trouble calling the right range to work with. Is there a simpler way to do what I want to do?
function statusupdate() {
var activesht = SpreadsheetApp.getActiveSpreadsheet();
var statussht = activesht.getSheetByName("Sheet 2"); //get sheet on which status update occurs
var statusrng1 = statussht.getRangeByName('B');
var statusrng2 = statussht.getRangeByName('C');
var mainsht = activesht.getSheetByName("Sheet 1"); //get sheet where you show a specific thing has been updated, if that thing mentioned here.
var mainrng = mainsht.getRangeByName('F');
if (statusrng1 == mainrng) {
var date = statusrng2.getValue();
var daterng = mainrng.getRangeByName('E');
daterng.setValues(date);
}
}
Spreadsheet formula
You can have the rows in one sheet follow those in another without using a script. For example, say we have a sheet named Items that contains one row for every item we carry, with the item number in the first column.
We can use VLOOKUP() to search for the row containing info about individual items, and select specific columns from it.
For example, this formula would be used in B2, and could be copied to other cells in our sheet:
=VLOOKUP($A2,Items!$A$2:$C$7,COLUMN(),false)
Script
There are a few issues with your script.
.getRangeByName('B') - This method gets a named range. Given the name, I suspect you mean to get column B, and NOT a named range. If that's the case, you could use this instead:
var statusrng1 = statussht.getRange('B:B');
In A1Notation, the range B:B is the entire column B.
You intend to copy values, so there is another step required beyond identifying ranges; you need to first read the values from a range, and then later write them to a different range. For that, you need to use methods like getValues() and setValues().
Here's an updated version of your script, adapted to the example spreadsheet described above.
function statusupdate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
//get sheet on which status update occurs
var statusSheet = ss.getSheetByName("Items");
var statusRange = statusSheet.getDataRange();
var statusData = statusRange.getValues();
//get sheet where you show a specific thing has been updated, if that thing mentioned here.
var trackingSheet = ss.getSheetByName("Tracking");
var trackingRange = trackingSheet.getDataRange();
var trackingData = trackingRange.getValues();
// Loop over all rows in the Tracking sheet to update from the Items sheet
// Start with row=1, because row 0 contains headers
for (var row=1; row<trackingData.length; row++) {
var item = trackingData[row][0];
if (item == '') continue; // skip rows without item #
// Look for item in Items sheet
var statusRow = null;
for (var sRow=1; sRow<statusData.length; sRow++) {
if (statusData[sRow][0] == item) {
// Found our match, grab that row
statusRow = statusData[sRow];
break;
}
}
// If we found a matching row, copy the status
if (statusRow) {
// Customize this depending on how your sheets are organized
trackingData[row][1] = statusRow[1];
trackingData[row][2] = statusRow[2];
}
}
// All values have been copied to trackingData, now write to sheet
trackingRange.setValues(trackingData);
}
I have a working script that upon form submit, specific rows move from one sheet to another. One of the fields I'm pushing is a url.
On the second sheet, the link is listed and it is hyperlinked, but it's really ugly and I really want to format it so that it shows "Edit" with a hyperlink. I've tried a number of ways, but my knowledge is limited so all I get are errors. I'm hoping someone can point me in the right direction.
Here is my code. I'm very new at this so the script is not at all sophisticated. Any help/suggestions would be appreciated!
function copyAdHoc(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = SpreadsheetApp.setActiveSheet(ss.getSheetByName("Form Responses 1"));
var data = sh.getRange(2, 1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
// Grab the Headers from master sheet
var headers = sh.getRange(1,1,1,sh.getLastColumn()).getValues();
var date = headers[0].indexOf('Effective Date');
var name = headers[0].indexOf('Employee Name');
var loc = headers[0].indexOf('Location');
var issue = headers[0].indexOf('Description/Question/Issue');
var add = headers[0].indexOf('Additional Information');
var change = headers[0].indexOf('Is this a Qualifying Life Event?');
var url = headers[0].indexOf('Form URL');
var category = headers[0].indexOf('Primary Category');
var status = headers[0].indexOf('Current Status');
var users = headers[0].indexOf('Users');
// Grab only the relevant columns
for(n = 0; n < data.length; ++n ) { // iterate in the array, row by row
if (data[n][change] !== "Yes" & data[n][category] !== "Employee Relations" & data[n][date] !== "") { // if condition is true copy the whole row to target
var arr = [];
arr.push(data[n][url]);
arr.push(data[n][users]);
arr.push(data[n][date]);
arr.push(data[n][loc]);
arr.push(data[n][name]);
arr.push(data[n][category]);
arr.push(data[n][issue] + ". " + data[n][add]);
arr.push(data[n][status]);
var sh2 = SpreadsheetApp.setActiveSheet(ss.getSheetByName("Ad Hoc")); //second sheet of your spreadsheet
sh2.getRange(sh2.getLastRow()+1,2,1,arr.length).setValues([arr]); // paste the selected values in the 2cond sheet in one batch write
}
}
}
It's a bit messy but the only way I know to achieve what you're trying to do would be to insert a column to the left of the hyperlink with the word Edit right justified and then remove the borders between the two.
From your description I am assuming you want the word "Edit" to be Hyperlinked. To do so, try this:
function getHyperlink(url)
{
return "=HYPERLINK(\""+url+"\","+"\"Edit\""+")";
}
function mainFunct()
{
//Do necessary steps
var tarLink = "https://www.google.com";
var tarRng = tarSheet.getRange(rowNum, colNum).setValue(getHyperlink(tarLink));
//perform other steps
}
EDIT:
Forgot to mention, since you're pushing your values to the array... you can do it in a similar way by either just storing the hyperlink in a variable or directly pushing it to the array like all the other values. Or if you're dealing with a hyperlink that has a static and dynamic part, For example: https://stackoverflow.com/questions/post_id, where post_id keeps changing but most of the URL is static, you can easily handle it by just passing the post_id to the getHyperlink function and getting the required Hyperlink in return. Hope this helps.