I've made a Google Sheet that serves to keep track of lot numbers within a manufacturing setting. Right now, when the onEdit trigger is enabled, the data from the source sheet only doubles first row of data in the destination sheet. Here's the link to the Sheet. This is the code I have so far:
function onEdit(e) {
if (e.range.getA1Notation() == 'N2') {
if (/^\w+$/.test(e.value)) {
eval(e.value)();
e.range.clear();
}
}
}
function Reset() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Input" ) {
var dataRange = s.getRange('A5:V14');
var values = dataRange.clearContent();
s.getRange("C5:C14").setValue('-');
s.getRange("F5:F14").setValue('-');
s.getRange("I5:I14").setValue('-');
s.getRange("L5:L14").setValue('-');
}
}
function Submit() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Input');
const targetSheet = ss.getSheetByName('Database');
const data = sh.getRange(5,1,1,23).getValues(); // A5:W14
targetSheet.getRange(targetSheet.getLastRow()+1,3,data.length,data[0].length).setValues(data);
}
If I understand your question correctly you are worried that when you select Submit, there is a double insert of data into the Database sheet.
It can be assumed that this is due to the Submit function being run twice. The first time when checking /^\w+$/.test(e.value) and the second time directly when you run eval(e.value)().
I'm not sure if this is a bug or a feature :-)
Try this code, it works:
function onEdit(e) {
if (e.range.getA1Notation() == 'N2') {
var val = e.value;
switch(val) {
case 'Reset': Reset();
break;
case 'Submit': Submit();
break;
}
e.range.clear();
}
}
Upd.
The double insertion is due to the fact that you are using 2 onEdit() triggers. Have a look at Edith / Triggers of the current project. Delete it.
Upd2.
If you want only filled rows out of 10 to appear on the Database sheet then use this code. It filters the rows based on whether there is some text in column H, such as Adult or Pediatric.
function Submit() {
var ss = SpreadsheetApp.getActive();
var sh = ss.getSheetByName('Input');
var targetSheet = ss.getSheetByName('Database');
var data = sh.getRange(5,1,10,23).getValues();
Logger.log(data);
var fData = data.filter(data => (data[13] != ""));
Logger.log(fData);
targetSheet.getRange(targetSheet.getLastRow()+1,3,fData.length,fData[0].length).setValues(fData);
}
Related
I've built a form system using Google Sheet. I'm wondering if there is a function to valid a cell entry if the user still has the cell selected.
Right now, if the user click on the save button, the change made in cell B7:G7 won't be saved.
So I'm looking for a function to add to my save script. Is there such a thing ?
function save() {
var sheet = SpreadsheetApp.getActiveSheet()
var db = SpreadsheetApp.openById("1OsPjUQDlq_mrCbnJspoTLFOamuIgGVp2fCeJc-tRq20").getActiveSheet()
var lastRow = db.getLastRow() + 1
// formula to validate cell entry
var id = sheet.getRange("G4:I4").getValue()
var date = sheet.getRange("B7:G7").getValue()
var idCol = 1
var dateCol = 2
db.getRange(lastRow, idCol).setValue(id)
db.getRange(lastRow, dateCol).setValue(date)
}
Activate the active range.
Example:
function save(){
SpreadsheetApp.getActiveRange().activate();
// do the whatever else should be done
}
Note: Activating a different range, will make that the value entered will be wrote to the activated range.
Here's a simple example:
code:
function submitData() {
const ss = SpreadsheetApp.getActive();
const ui = SpreadsheetApp.getUi();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange(2, 2, sh.getLastRow() - 1).getValues().flat();
let valid = true;
vs.forEach((e, i) => {
if (!e) {
ui.alert(`No data at B${i + 2}`);
valid = false;
}
});
if (valid) {
ui.alert('Data was submitted');
} else {
ui.alert('Data not submitted');
}
}
Form:
Desc
Data
First
First
Last
Last
MI
MI
Demo:
Does anyone have any sample code for this? Trying to set up a daily checklist for a team to use to note that they completed a task. Trying to get it so they would just have to check the box in the Google sheet next to the task and in the field to the right, it would auto-insert their name by looking it up in our Google Workforce Directory. Any help would be appreciated.
Insert Users Name
function onEdit(e) {
//e.source.toast("Entry");
Logger.log(JSON.stringify(e));
const sh = e.range.getSheet();
const checkboxrow = ;//Enter checkbox row
const checkboxcol = ;//enter checkbox col
const destinationRange = "";//Enter destination range in a1 notation
if (sh.getName() == " Enter Your Sheet Name" && e.range.rowStart == checkboxrow && e.range.columnStart == checkboxcol && e.value == "TRUE") {
sh.getRange(destinationRange).setValue(e.user.nickname);
}
}
This simple task took surprisingly many lines of code:
// global variables
var SS = SpreadsheetApp.getActiveSpreadsheet(); // spreadsheet
var checkbox_col = 1; // column with checkboxes
var sheet_name = 'Sheet1'; // sheet name
// main function
function insert_user(e) {
try {
var {rowStart, columnStart} = e.range; // get row and column that was edited
if (columnStart != checkbox_col) return; // do nothing if it was not column with checkboxes
var sheet = e.source.getActiveSheet(); // get current sheet
if (sheet.getName() != sheet_name) return; // do nothing if the sheet has another name
if (e.value != 'TRUE') return; // do nothing if the checkboxs was unchecked
var user = getOwnName(); // get the user name
sheet.getRange(rowStart,checkbox_col+1).setValue(user); // set user name into the next cell
}
catch(err) { SpreadsheetApp.getUi().alert(err) }
}
// additional functions ------------------------------------------------------
// remove all triggers
function remove_all_triggers() {
ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
SS.toast('All triggers were remoded');
}
// install onEdit trigger
function install_onEidt_trigger() {
ScriptApp.newTrigger('insert_user').forSpreadsheet(SS).onEdit().create();
SS.toast('Trigger was installed');
}
// custom menu to install and remove triggers
function onOpen() {
SpreadsheetApp.getUi().createMenu('⚙️ Scripts')
.addItem('Install trigger', 'install_onEidt_trigger')
.addItem('Remove all triggers', 'remove_all_triggers')
.addToUi();
}
// get user name, credits: https://stackoverflow.com/a/15628478/14265469
function getOwnName(){
var email = Session.getEffectiveUser().getEmail();
var self = ContactsApp.getContact(email);
if (self) {
var name = self.getGivenName();
if (!name) name = self.getFullName();
return name;
}
else {
var userName = Session.getEffectiveUser().getUsername();
return userName;
}
}
First you will need to reload your spreadsheet and install the trigger via custom menu.
After that whenever one click on checkboxes in column 'A' on the sheet with a name 'Sheet1' it will add user name in column 'B':
I want to create a script that moves data from one sheet to another when I mark it as completed in a particular column. Using some other code I found on the internet, I have this, but when I go in and change that status to completed nothing happens. The trigger page in google apps script says it's executing, but it isn't doing anything to the actual sheet. Here is the code:
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
;}
return (ct);
}
}
I assume something is wrong with it but I don't know what. I've never used apps script before so I honestly don't know what I'm doing. Here is the sheet:
Sheet
I want to move completed homework from the planner sheet to the calendar sheet when I change the status. Thanks so much for any help!!
EDIT:
I used lamblichus's code and it works great except that I still want to delete the data from the Planner Sheet after I move it. I tried this code and it didn't work:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherData = range.offset(0,-3,1,3).getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
var initialSheet = ss.getSheetByName("Planner");
var initialRange = initialSheet.range.offset(0,-3,1,3);
initialRange.clearContent(); //delete values from Planner
}
}
Issues and solution:
There are several issues with your current code:
If you want to check the sheet name, you have to use Sheet.getName(). SpreadsheetApp.getActiveSpreadsheet() just returns the active spreadsheet, not sheet, and not its name anyway.
If you want to get values from multiple cells, you should use getValues(), not getValue().
The third parameter of offset corresponds to the number of rows of the resulting range. Therefore, it should not be 0.
The "Class" name is in a merged range, and only the top-left cell in a merged range includes the corresponding value. To get that value, you can use getMergedRanges and retrieve the first element in the resulting array. Since getValue() returns the value in the top-left cell of a range, it will return the "Class" name.
Code sample:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherDataRange = range.offset(0,-3,1,3);
var otherData = otherDataRange.getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
otherDataRange.clearContent();
}
}
It looks like a syntax error on line 14, you put ;}, it should be }; you don't need to tell JavaScript (the coding language that AppScript is based on) when you end comments. But it likes it when you tell it when you end while loops.
Here is the updated code.
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
};
return (ct);
};
}
I am totally new at this. I saw a script for data input without forms. I followed the script suggested elsewhere but it's not updating the values in the specified sheet. Can formatting in the datasheet be the problem? should i attach the file?
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("New Data"); //Data Entry Sheet
var datasheet = ss.getSheetByName("Business Info"); //Business Info Data Sheet
//Input Values
var values = [[formSS.getRange("B3").getValue(),
formSS.getRange("B4").getValue(),
formSS.getRange("B5").getValue(),
formSS.getRange("B6").getValue(),
formSS.getRange("B7").getValue(),
formSS.getRange("B8").getValue(),
formSS.getRange("B9").getValue(),
formSS.getRange("B10").getValue(),
formSS.getRange("B11").getValue(),
formSS.getRange("B13").getValue(),
formSS.getRange("B14").getValue(),
formSS.getRange("B15").getValue(),
formSS.getRange("B16").getValue(),
formSS.getRange("B17").getValue(),
formSS.getRange("B18").getValue(),
formSS.getRange("B19").getValue(),
formSS.getRange("B20").getValue(),
formSS.getRange("B21").getValue(),
formSS.getRange("B23").getValue(),
formSS.getRange("B24").getValue(),
formSS.getRange("B25").getValue(),
formSS.getRange("B26").getValue(),
formSS.getRange("B27").getValue(),
formSS.getRange("B28").getValue(),
formSS.getRange("B29").getValue(),
formSS.getRange("B30").getValue(),
formSS.getRange("B31").getValue(),]];
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 27).setValues(values);
}
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName("New Data");
var rangesToClear = ["B3","B4","B5","B6","B7","B8","B9","B10","B11","B13","B14","B15","B16","B17","B18","B19","B20","B21","B23","B24","B25","B26","B27","B28","B29","B30","B31"];
for (var i=0; i<rangesToClear.length; i++) {
sheet.getRange(rangesToClear[i]).clearContent();
}
}
function onEdit(e) {
e.source.setActiveSelection(e.range.offset(1, 0));
}
You can do it like this:
function submitData() {
const ss=SpreadsheetApp.getActive();
const fsh=ss.getSheetByName("New Data");
const frg=fsh.getRange(1,2,31,1);
const vs=frg.getValues();
const dsh=ss.getSheetByName("Business Info");
const out=new Set([1,2,12,22]);
let values=vs.map(function(r,i){if(!out.has(i+1))return r;}).filter(function(e){return e;});
dsh.getRange(dsh.getLastRow()+1,1,1,27).setValues(values);
//clear1();
}
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName("New Data");
var rangesToClear = ["B3","B4","B5","B6","B7","B8","B9","B10","B11","B13","B14","B15","B16","B17","B18","B19","B20","B21","B23","B24","B25","B26","B27","B28","B29","B30","B31"];
for (var i=0; i<rangesToClear.length; i++) {
sheet.getRange(rangesToClear[i]).clearContent();
}
}
function onEdit(e) {
const sh=e.range.getSheet();
if(sh.getName()!="New Data")return;
e.source.setActiveSelection(e.range.offset(1, 0));
}//I'm guessing your doing this to get the cursor to move down after every entry but you will have problems on rows 12 and 22
And then you can create and install a submit button as shown here. I'm assuming that you want to run the submitData() function when you click the button and possibly add the command to call clear1 at the end of submitData(). You could also use the sidebar to create html buttons which can do the same thing but they use google.script.run to call any function on the server.
You don't need to get the range of each cell. You can just specify a multi-cell range and use getValues(). This will return a 2D array, which you can convert to a 1D array with map() or with flat(). This last method is used in the sample below:
function submitData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const formSS = ss.getSheetByName("New Data");
const datasheet = ss.getSheetByName("Business Info");
const values = formSS.getRange("B3:B31").getValues().flat();
datasheet.getRange(datasheet.getLastRow() + 1, 1, 1, values.length).setValues([values]);
}
By using values.length, you can make your destination range have a dynamic number of columns, depending on the input range.
You can also greatly simplify your function clear1 by doing this:
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName("New Data");
var rangeToClear = "B3:B31";
sheet.getRange(rangeToClear).clearContent();
}
Reference:
Range.getValues()
I have a Spreadsheet with some functions. One of them is a onEdit(event) function that copies some values to other sheets based on conditions. This is the code (simplified but with the important parts intact):
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.range;
if(s.getName() === "Lista" && r.getColumn() === 9 && r.getValue() === "Posicionada") {
var sheetname = s.getRange(r.getRow(),3).getValue();
var columnRef = s.getRange(r.getRow(),4).getValue();
var row = s.getRange(r.getRow(),5).getValue();
var targetSheet = ss.getSheetByName("Mapa " + sheetname);
var headers = targetSheet.getRange(1, 1, 1, targetSheet.getLastColumn());
for (var i = 0; i < headers; i++) {
if (headers[i] === columnRef) {
break;
}
}
var column;
if (columnRef === "A1") {
column = 2;
}
else if (columnRef === "A2") {
column = 3;
}
else if (columnRef === "B1") {
column = 4;
}
else if (columnRef === "B2") {
column = 5;
}
if (sheetname === "N2") {
row = row - 30;
}
if (sheetname === "N3") {
column = column - 10;
row = row - 42;
}
targetSheet.getRange(row,column).setValue(s.getRange(r.getRow(), 1, 1, 1).getValue());
}
}
The code works as it should when I manually edit the cell. But, I have a code that edit the cell when the user press a button in a sidebar, this is the code:
function positionMU(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveCell().activate();
var cellLevel = cell.offset(0,2);
var cellLetter = cell.offset(0,3);
var cellNumber = cell.offset(0,4);
var cellStatus = cell.offset(0,8);
var dbq = "Posicionada";
var fora = "Pendente de recebimento";
if (cellStatus.getValue() == "Aguardando posicionamento"){
cellStatus.setValue(dbq); //attention in this line
}
else if (cellStatus.getValue() == "Aguardando saída"){
cellStatus.setValue(fora);
var cellExitDate = cell.offset(0,6);
cellExitDate.setValue(getDate());
}
}
As you can see, this function change the cell content with setValue(), but, when I use this function, the value of the cell changes, but the onEdit() trigger doesn't work.
How can I make the onEdit() trigger recognize changes made with setValue()?
You are right. onEdit() only triggers if the range is edited manually. As can be seen here, onEdit() triggers when a value is changed by the user.
I tested the function by making function to insert values into a column for which my onEdit responds and nothing happens. Including various other techniques that I could think of. Best thing to do here is to suggest this as an enhancement on App Script's Issue Tracker.
However, I made it work by writing another function to be called when another function in the script makes changes to the sheet. These are the test functions I wrote:
function addValues()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var book = "Book";
var cancel = "Cancel";
var maxRow = range.getLastRow()+1;
for(var i=0; i<4; i++)
{
if (i%2 == 0)
{
sheet.getRange(maxRow, 1).setValue(book);
autoChanges(maxRow);
}else{
sheet.getRange(maxRow, 1).setValue(cancel);
autoChanges(maxRow);
}
maxRow++;
}
}
autoChanges function:
function autoChanges(row)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var data = range.getValues();
var response = "";
sheet.getRange(row, 2).protect();
response = data[row-1][0];
if (response == "Book")
{
sheet.getRange(row, 2).canEdit();
}else{
sheet.getRange(row, 2).setValue("--NA--");
}
}
Not the most elegant solution but this seems to be the only workaround for what you are trying to do.
There are some very good reasons why calling range.setValue() doesn't trigger the onEdit event, and most of them have to do with infinite recursion. In fact, you call setValue() yourself WITHIN onEdit(). This would trigger a recursive call, and from what I can see, you have no provision for handling the base case and thus your code would explode if setValue() did what you want.
Why not simply take all of your code out of your event handler, and put it into another function:
function onEdit (e) {
return handleEdits(e.range);
}
function handleEdits(r) {
s = r.getSheet();
ss = s.getParent();
//the rest of your code should drop right in.
}
then, inside your autoChanges function, go ahead and call handleEdits, passing it an appropriate range, after your call to setValue().
If you like to play with fire, and I personally do, you can call the onEdit(e) function after you make a change. Just send in an object whatever is called and used by the e object.
For me, I just needed to add:
var e={};
e.range=the range you are making a change to in the script;
e.source = SpreadsheetApp.getActiveSpreadsheet();//or make sure you have the sheet for wherever you are setting the value
e.value=whatever value you are setting
e.oldValue=if you onEdit needs this, set it here
//if you are using any of the other standard or special properties called by your onEdit...just add them before calling the function.
onEdit(e);//call the function and give it what it needs.