Working with a Google Sheets where every line includes an ID and a picklist value. Example: Google Sheet.
What I am trying to do is run a custom function when someone edits the picklist cell. The function takes two arguments, the value from the ID and the picklist cell of the same line, then performs a HTTP POST request to update the record in our CRM.
//When STAGE cell on Google Sheet is updated, run this function:
function updateProjectStage(status, id) {
var baseURL = 'https://crm.zoho.com/crm/private/json/Potentials/updateRecords?authtoken=xxx&scope=crmapi&id=', // see docs https://www.zoho.com/crm/help/api/updaterecords.html
recordID = id, // building id from A column
stage = '<Potentials><row no="1"><FL val="Stage">' + status + '</FL></row></Potentials>'; // status from B column
var postURL = baseURL + recordID + '&xmlData=' + stage;
Logger.log(postURL);
var response = UrlFetchApp.fetch(postURL); // update record in crm
var sanitizedResponse = JSON.parse(response.getContentText()); // get confirmation/failure
Logger.log(sanitizedResponse);
}
I don't know how to run the function for this picklist type of cell - I cannot just input =updateProjectStage(status, id) into the cell like I am used to doing because it errors out.
Example: Error Message.
Is this even possible?
Your answer lies in capturing the edit event when the user modifies any cell on the sheet. The user can modify any cell, of course. Your job is to determine if that cell is in the range you care about. The onEdit event can be captured using this function:
function onEdit(eventObj) {
//--- check if the edited cell is in range, then call your function
// with the appropriate parameters
}
The object passed into the event describes the cell that was edited. So we set up a "check range" and then make a comparison of that range to whichever cell was edited. Here's the function:
function isInRange(checkRange, targetCell) {
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
//--- the target cell is in the range!
return true;
}
The full event function for the edit event would be
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var checkRange = SpreadsheetApp.getActiveSheet().getRange("B2:B10");
if (isInRange(checkRange, eventObj.range)) {
//--- the ID cell is on the same row, one cell to the left
var idCell = eventObj.range.offset(0,-1);
//--- the status cell is the one that was edited
var statusCell = eventObj.range;
updateProjectStage(statusCell, idCell);
}
}
Here's the whole thing all together:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var checkRange = SpreadsheetApp.getActiveSheet().getRange("B2:B10");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
//--- the ID cell is on the same row, one cell to the left
var idCell = eventObj.range.offset(0,-1);
//--- the status cell is the one that was edited
var statusCell = eventObj.range;
updateProjectStage(statusCell, idCell);
} else {
Logger.log('must be outside the range');
}
}
function updateProjectStage(status, id) {
Logger.log('we are updating');
}
Related
I have a spreadsheet I designed in Google Sheets to input data at work and then a formula that determines if the part needs to be replaced and provides the part number required. I need either a macro or appScript that will start with a certain cell on the same sheet, highlight it, allow me to type a value in it, then either by pressing the ENTER or TAB key to move to the next cell on the page (Not necessarily the next door cell, but a cell in another column and/or row), -AND- based on a data validation check box determine which cells are selected. How do I write either a macro or appScript to do what I need? Which would be easier?
Reference
sheet.setActiveSelection()
Script:
Try
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName'){ // adapt
var addresses = ["E7","H7","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]);
}
}
}
Note:
This way you can determine the order in which the cells are selected.
If you have a script that copies cells into a master data sheet, you can take advantage of the range list. (by the way, you can find here how to transfer the data).
In case of protected sheet:
If your sheet is protected, except for the cells that need to be filled in, you can use a script that will search for them and reorganize them.
function onEdit(event) {
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName') { // adapt
var addresses = listOfUnprotectedRanges()
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1) {
sh.setActiveSelection(addresses[item + 1]);
}
}
}
function listOfUnprotectedRanges() {
var p = SpreadsheetApp.getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
var ranges = p.getUnprotectedRanges().map(r => [r.getA1Notation(), r.getRow(), r.getColumn()])
ranges = ranges.sort(function (a, b) { return a[2] - b[2]; }); // sort by columns
ranges = ranges.sort(function (a, b) { return a[1] - b[1]; }); // sort by ranges first
return ranges.map(r => r[0])
}
I'm trying to setup a daily report that will send me an email with only the edited rows from a sheet, but also including the old values before they were edited.
I wrote the following script, where when the specified range is edited, the whole row is transferred to another tab called "new_values".
function onEdit_inRange(e,sheetName,sheetRange){
var sh=e.range.getSheet();
if(sh.getName()===sheetName){
var range = SpreadsheetApp.getActiveSheet().getRange(sheetRange);
var xy=[e.range.getColumn(),e.range.getRow()];
if (xy[0]>=range.getColumn() && xy[0]<=range.getLastColumn() && xy[1]>=range.getRow() && xy[1]<=range.getLastRow()) {
return e.range;
}
}
return false;
}
function onEdit(e){
var range=0;
if((range=onEdit_inRange(e,'sheet','M6:O'))){
// SpreadsheetApp.getUi().alert('You Edited a Cell INSIDE the Range!');
} else if ((range=onEdit_inRange(e,'sheet','Q6:Q'))) {
// SpreadsheetApp.getUi().alert('You Edited a Cell INSIDE the Range!');
}
var ss = e.range.getSheet();
//ss.getRange(e.range.getRow(),49) //set timestamp in column 49
//.setValue(new Date()); // Set time of edit in "G"
var appendSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('new_values');
var archiveLastRow = appendSheet.getLastRow();
Logger.log(archiveLastRow);
var archiveAppendRange = "new_values!" + (appendSheet.getLastRow() + 1) + ":" + (appendSheet.getLastRow() + 1);
Logger.log(archiveAppendRange);
var destRange = ss.getRange(archiveAppendRange);
Logger.log(destRange);
var sourceDataValues = ss.getRange(range.getRow(),1,1,49).copyTo(destRange);
Logger.log(sourceDataValues);
}
What I can't manage to do is to make this script to also copy the old values, when they are edited, and move them to another separate tab (called "old_values").
How can I copy the old value, when cells are edited in the same way I do the new values?
My plan is that I'll merge both tables and send over email.
Here's an example spreadsheet with my code: https://docs.google.com/spreadsheets/d/1xzyVD7ElA0FMVC8sta6beDJCIwOQEMRqjhF5eW4mExI/copy
I have a data sheet, containing:
|ItemCode | Item | Version|
The goal is to get the last version of the item and increment it by 0.1, so if the item's version is 03, then its new version would be 3.1 once this function was called.
The code below finds the first, but not the last occurrence in range(data sheet). I need to find the last version for that item and increment it:
function newVersion() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("ArquivoItens");
var range = sheet.getRange(2,1,ss.getLastRow(),3).getValues();
var editarSheet = ss.getSheetByName('EditarItem');
var itemCode = editarSheet.getRange("W5").getValue();
var version = editarSheet.getRange("AC4").getValue();
for(var a = 0; a < range.length; a++){
if(range[a].lastIndexOf(itemCode)>-1){
Logger.log("Código: "+ itemCode);
Logger.log("Versão: "+ range[a][2]);
Logger.log("Produto: "+ range[a][1]);
Logger.log(range[a].indexOf(itemCode));
return range[a][2];
}
}
Any light is appreciated.
function findTheLastVersionAndAdd(itemcode) {
if(itemcode) {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const sr=2;//guess start row is 2
const vs=sh.getRange(sr,1,sh.getLastRow()-sr+1,3).getValues();
var iObj={itemcode:''};
vs.forEach(function(r,i){
if(iObj.hasOwnProperty(r[0])) {
iObj[itemcode]=i+sr;//updates the object if r[0]==itemcode
}
});
return Number(sh.getRange(iObj[itemcode],3).getValue()) +.1;
}
}
After your comments and explanations, I made this code that will help you to achieve what you desire:
function newVersion(e){
var itemCell = e.source.getActiveCell();
var col = itemCell.getColumn();
// If the Column is the one where the items are then continue
if(itemCell.getColumn() === 2){
var ui = SpreadsheetApp.getUi();
var result = ui.alert('Please confirm','Are you sure you want to continue?',
ui.ButtonSet.YES_NO);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
// Get the item's version and increment its value
var versionCell = itemCell.offset(0, 1);
var itemVersion = versionCell.getValue();
var updatedVersion = itemVersion + 0.1;
versionCell.setValue(updatedVersion);
} else {
// User clicked "No" or X in the title bar.
ui.alert('Permission denied.');
// Return to value previously edited
itemCell.setValue(e.oldValue);
}
}
}
What the code will do is when you change a value in an item, you will get a pop-up window asking you to confirm it. If you do confirm it then using the offset(rowOffset, columnOffset) method you'll get the item's version cell and in that way, you'll be able to increment it. If you do not confirm it, then the value in your item will come back to be the old one. Notice e represents the Event Object.
Take into consideration I'm using an Installable Trigger, for setting it up, do the following:
1) Go to your Apps Script project
2) Click Edit->Current project's triggers
3) Click "+ Add Trigger"
4) Select :
Choose which function to run -> Function Name
Select event source-> From spreadsheet
Select event type -> On edit
5) Click Save
I have data in 6 columns and then a column "Last updated" where I manually write the date of the last change in the row. I am sure there is a way to automatize this, but cannot make it work.
note - I do not now how to code, just tried to modify multiple codes I found on the web and nothing worked for me, I am getting desperate.
It is basically a database of chemical substances, where each column is a property, and I need to stamp the last time any of the properties were changed. Can anyone please help me with this?
You will have to enter some code in order to make this work. This link gives a good example on how to create a script for your sheet.
The most important part of your script is that you want it to run EVERY time you make a change to the sheet in ANY cell. Then when it runs, you can check which cell was changed and them determine if that cell is in the area you're interested in checking. Consider a sheet set up like this one:
When you have your sheet set up, then go to the Tools menu and select Script Editor. From there you'll enter the code below. What I'm giving you is an example that you must change to meet your needs. The first part to understand is the function onEdit. This is a specially named function that will run EVERY time a change is made to the sheet. So when you have
function onEdit(eventObj) {
}
Then anything you put inside the braces will execute each time a change is made (after the user types Enter). So your onEdit function looks like this:
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
The eventObj is the cell that the user edited and changed. So we are using that cell to compare with our area to check. If we determine that the cell is within that area, then we go ahead and define the cell to hold the timestamp and then assign a value to it. The value is the formatted date and time.
The important piece here is the checkRange variable that is defined as the area of all your properties. So when the onEdit function runs, it defines the area you're checking, then calls isInRange to see if the cell that was edited is within that area. Here is the function:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
Your result will look like this:
So now, all together, just copy and paste this code into your script editor:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
}
It's not necessary to use scripts. It may be easier to use the formula:
=(your_datetime - date(1970;1;1) ) * 86400000
timestamp-to-date-and-back-screenshot
Check the example here: https://docs.google.com/spreadsheets/d/1JHr4z3-32fWYUAjm3bG_Wyr-ZXn7FUvrECa3B8TtJtU/edit?usp=sharing
I'm working on the script below (keep in mind that it's far from done):
function Import() {
var dealCells = getRange('Deals');
var blank = new Array ("");
if (blank.indexOf(dealCells.getValue()) != -1) {
if (Browser.msgBox('You have not entered all the Deal IDs, do you want to continue anyway?', Browser.Buttons.YES_NO) == 'no') {
Browser.msgBox('Please fill in all the Deal IDs')
if (blank.indexOf(dealCells.getValue()) == -1) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Worksheet");
var cell = sheet.getRange(2,12);
cell.setValue("IMPORT");
}
}
}
}
I'm trying to determine whether or not there is an empty cell in the range A4:A23 in the worksheet 'Newsletter'.
If there is an empty cell: prompt the yes/no message box
If yes: continue with setting the cell value to 'IMPORT' and finish the script without further actions
If no: prompt the second message box and finish the script without any further action
Assuming that dealCells is A4:A23:
function Import() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Newsletter');
// Get range values
var dealCells = sh.getRange("A4:A23").getValues()
// Flatten array for `indexOf()`
var blank = dealCells.join().split(',');
if (blank.indexOf("") != -1) { // Check if there is a blank cell
// If a blank cell exist
var response = Browser.msgBox('You have not entered all the Deal IDs, do you want to continue anyway?', Browser.Buttons.YES_NO)
// Handle the msgBox response
if (response == "yes") {
var value = Browser.msgBox('Please fill in all the Deal IDs')
} else { //response == no
...
}
} else { // no blank cell
...
}
...
}
edit you can also getRangeByName():
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dealCells = ss.getRangeByName('Deals').getValues();