My work has a Google Form where different information is inputted. All this information is then automatically put into a Google Sheet. We will call it "All data sheet".
"All data sheet" has multiple sheets in it, one with all of the data and then one for each type of data which needs to be given to different people. One of those is called 'Caretaker'.
I then have a second Google Sheet (example called 'Sorting') that pulls everything from the 'Caretaker' sheet onto 'Import_range'.
This allows the caretaker to see everything that needs fixing with the oldest thing at the top and the newest at the bottom.
The caretaker would like the newest at the top.
I can easily make another sheet (see 'Sorted') that uses the sort function to put the newest at the top.
However, he would also like another column where he can either check a box to show it's done or write an x.
When new data appears, the checked box does not move down.
Is there any way to sort the data but also have an input column which stays with the sorted data?
Link to example.
You have to use Google Apps Script.
Try this:
To start, go to Tools and click Script editor.
Delete the code in Code.gs
Paste the code provided below and save
Refresh your Spreadsheet and Custom Menu will pop up in the Menu
Go To Custom Menu and Click Update Sorted Sheet
First execution will require user's authorization. Once you authorized the script, make sure to rerun the Custom Menu.
The code below will create a Custom Menu in your Spreadsheet. Clicking the custom menu will execute a function that will update the Sorted Sheet and retain its checkbox values.
I also added comments to explain the function of each line in the code.
Code:
function updateSorted() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Import_range");
var sortedSh = ss.getSheetByName("Sorted");
/* Use this if the Import_range is from different spreadsheet
var iSS = SpreadsheetApp.openById("Insert Sheet ID here");
var sh = iSS.getSheetByName("Insert Sheet name here");
and comment the var sh = ss.getSheetByName("Import_range"); above*/
/*
Check if the Sorted Sheet has data.
If true, compare Sorted Sheet and Import_range data
else, copy Import_range data to Sorted Sheet.
*/
if(sortedSh.getLastRow() > 1){
var sData = sortedSh.getRange(2, 1, sortedSh.getLastRow()-1, 5).getValues(); //remove unnecessary data
var sDataFiltered = sData.filter(e => e[4]); //get Date column of Sorted sheet
var sDates = sortedSh.getRange(2, 5, sortedSh.getLastRow()-1, 1).getValues().flat();
var sDatesFiltered = sDates.filter((a) => a); //remove unnecessary data
var importDates = sh.getRange(2, 4, sh.getLastRow()-1, 1).getValues().flat(); //get Date column of Import_range
//convert dates to String format
var arr1 = sDatesFiltered.map( dateString => String(dateString))
var arr2 = importDates.map( dateString => String(dateString))
var difference = arr2.filter(x => !arr1.includes(x)); //get the difference between arr1 and arr2
difference.forEach(dates => {
var index = arr2.indexOf(dates); //get new entry position in Import_range
var newEntry = sh.getRange(index+2, 1, 1, 4).getValues().flat(); //get row data of new entry from Import_range
newEntry.unshift(false); //append false in the beggining of the array
sDataFiltered.push(newEntry); //append new entry to sDataFiltered array
})
var range = sortedSh.getRange(2, 1, sDataFiltered.length, 5);
range.setValues(sDataFiltered); //set the values of Sorted sheet using the sDataFiltered array as data
sortedSh.getRange(2, 1, sDataFiltered.length, 1).insertCheckboxes(); //get the checkbox range and insert checkbox
range.sort({column: 5, ascending: false}); //sort the data by Date column
}else{
var range = sh.getRange(2, 1, sh.getLastRow()-1, 4);
var data = range.getValues();
data.forEach(row => {
row.unshift(false)
})
var sortedRange = sortedSh.getRange(2, 1, data.length, 5);
sortedRange.setValues(data);
sortedSh.getRange(2, 1, data.length, 1).insertCheckboxes();
sortedRange.sort({column: 5, ascending: false});
}
}
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('Update Sorted Sheet', 'updateSorted')
.addToUi();
}
Examples:
Populating Empty Sorted Sheet:
Updating Import_range sheet, clicking checkboxes and updating Sorted Sheet:
Implementing Time Driven Triggers:
Open your Apps Script project.
At the left, click Triggers alarm.
At the bottom right, click Add Trigger.
Select and configure the type of trigger you want to create.
Click Save.
For time driven, your trigger setup should somehow look like this:
The example trigger above will update the sheet every minute. The down side of using time driven trigger is the quota limit. Gmail accounts are limited to 90 minutes of total runtime. Exceeding quotas will result to errors. See Quotas for Google Services.
Using Buttons: Alternative to Custom Menu
To create a user friendly button in your Sheet
Go to Insert -> Drawing
Draw the desired button shape and style.
Click Save and Close
Move the button to your desired position
Right click, click the 3 vertical dots and select Assign script
Type updateSorted and click OK
Click the button to run
Note: If you decided to use buttons and want to remove the Custom Menu in your sheet, just remove the onOpen function in your Apps Script.
References:
Google Apps Script on Sheets
Custom Menu
Simple Triggers
Class Sheet
Class Range
Class Spreadsheet
Installable Trigger
I have a form that has a Question that depending upon an answer will direct you to futher questions. The problem is, once you submit the form and then go back and edit your answer, if you choose another answer for the first question and it redirects to a new question because your first answer was different, the response in Google Sheets keeps both answers. How can I fix this so that it only shows the last answers? For example. When I first answer the question, I choose Option 1.
Then when I choose next I get these options.
Which once I submit will give the following response submission in Sheets.
Ok, so then if I go back and Edit my response like so...
It will redirect to a different question based on my first choice.
However, once I submit my final answer, this is what the subission looks like in Sheets.
The problem is, Column C and D answers are no longer relevant based upon the answer in Column B. How can I fix this so that Column C and D are blank as they should be and I only get the answer that is correct based upon the submission in Forms?
Thanks!
You can create a bound script in your Google Sheets with a Form Submit trigger.
Sample Code:
function formSubmission(e) {
Logger.log(JSON.stringify(e));
Logger.log(e.values);
var data = e.values;
var row = e.range.rowStart;
var sheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1');
if(data[1]=="Option 1"){
//Remove data under Follow-up Question2 and Amount 2 (Column E and F)
sheet.getRange(row,5,1,2).clearContent();
}else if(data[1]=="Option 2"){
//Remove data under Follow-up Question1 and Amount 1 (Column C and D)
sheet.getRange(row,3,1,2).clearContent();
}
}
Pre-requisite:
What it does?
Get the form submitted values using Sheets form submit event object values
Get the form submitted target row using range.rowStart
Get the active spreadsheet and sheet with the name Form Responses 1
Check the Option selected and delete specific columns depending on the option selected.
Output:
Here's how I finally was able to do what I needed Ron M's help:
function formSubmission(e) {
Logger.log(JSON.stringify(e));
Logger.log(e.values);
var data = e.values;
var row = e.range.rowStart;
var sheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1');
if(data[4]=="Debit"){
sheet.getRange(row,7,1,1).clearContent();
}else if(data[4]=="Credit"){
sheet.getRange(row,6,1,1).clearContent();
}
if(data[7]=="INCOME"){
sheet.getRange(row,10,1,14).clearContent();
}else if(data[7]=="HOME"){
sheet.getRange(row,9,1,1).clearContent();
sheet.getRange(row,11,1,13).clearContent();
}else if(data[7]=="LIVING"){
sheet.getRange(row,9,1,2).clearContent();
sheet.getRange(row,12,1,12).clearContent();
}else if(data[7]=="TRANSPORTATION"){
sheet.getRange(row,9,1,3).clearContent();
sheet.getRange(row,13,1,11).clearContent();
}else if(data[7]=="HEALTH"){
sheet.getRange(row,9,1,4).clearContent();
sheet.getRange(row,14,1,10).clearContent();
}else if(data[7]=="INSURANCE"){
sheet.getRange(row,9,1,5).clearContent();
sheet.getRange(row,15,1,9).clearContent();
}else if(data[7]=="EDUCATION"){
sheet.getRange(row,9,1,6).clearContent();
sheet.getRange(row,16,1,8).clearContent();
}else if(data[7]=="CHARITY/GIFTS"){
sheet.getRange(row,9,1,7).clearContent();
sheet.getRange(row,17,1,7).clearContent();
}else if(data[7]=="SAVINGS"){
sheet.getRange(row,9,1,8).clearContent();
sheet.getRange(row,18,1,6).clearContent();
}else if(data[7]=="OBLIGATIONS"){
sheet.getRange(row,9,1,9).clearContent();
sheet.getRange(row,19,1,5).clearContent();
}else if(data[7]=="ENTERTAINMENT"){
sheet.getRange(row,9,1,10).clearContent();
sheet.getRange(row,20,1,4).clearContent();
}else if(data[7]=="PETS"){
sheet.getRange(row,9,1,11).clearContent();
sheet.getRange(row,21,1,3).clearContent();
}else if(data[7]=="SUBSCRIPTIONS"){
sheet.getRange(row,9,1,12).clearContent();
sheet.getRange(row,22,1,2).clearContent();
}else if(data[7]=="VACATION"){
sheet.getRange(row,9,1,13).clearContent();
sheet.getRange(row,23,1,1).clearContent();
}else if(data[7]=="MISCELLANEOUS"){
sheet.getRange(row,9,1,14).clearContent();
}
}
I want to import data from google spreadsheet to another sheet along with the color which is present in original sheet.
The suggested solution is below:
function onOpen() {
var destination = SpreadsheetApp.getActive(),
sheetName = 'Sheet1';
var previousCopy = destination.getSheetByName(sheetName);
if (previousCopy) previousCopy.setName('Dirty');
}
function getSheetCopy() {
var destination = SpreadsheetApp.getActive();
var dirty = destination.getSheetByName('Dirty');
Logger.log(dirty);
if (!dirty) return;
destination.deleteSheet(dirty);
// Replace sourceId by required identifier,
// and replace sheetName value as required
var sourceId = '1el850cyzxy4PrL2uDhN7Dr9vWNX3cGA5-ks1amAX-Ak',
sheetName = 'Sheet1';
var source = SpreadsheetApp.openById(sourceId);
source.getSheetByName(sheetName).copyTo(destination).setName(sheetName);
}
The idea is to make a full copy (mirror) of the source sheet every time we open a target (destination) spreadsheet. onOpen trigger only marks the previous copy as "Dirty". It has limited AuthMode and can not do updates itself.
First time you should make empty "Sheet1" to get ready for automated updates...
Time triggered getSheetCopy does the main work and removes "Dirty" mark once. Trigger period is 1 minute (minimal). So when open a spreadsheet, you can see if the mirrored sheet is "Dirty" (not updated) or already actual.
I'm trying to have a button to allow users to upload a file, into a specific folder. I tried to follow other advise, and add this hook to
onPickerInit:
var uploadView = new google.picker.DocsUploadView()
uploadView.setParent('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); // test folder
pickerBuilder.addView(uploadView);
I've set the MULTISELECT_ENABLED feature (without it, the destination folder is not respected), and I can in fact now upload the files where they belong. Yay!
HOWEVER: The picker widget now has two upload tabs. The first one just does the regular upload into the drive main folder, the second tab does the right thing. My guess is that appmaker contstructs the first upload tab behind the curtains and there is no feature to disable this.
This is obviously fairly quirky and hardly usable. My questions are:
1) are there (possibly undocumented) API calls in the pickerbuilder to remove the original upload view?
2) Is it possible to respect the destination folder even is the MULTISELECT feature is off ?
Many thanks in advance for any pragmatic solutions!
EDIT 07/28/2020
Due to the constant changes in drive picker, this code aims for a more permanent solution:
var folderId = "10fYS3l32R6gk79POOSS8X_Vbsz7vqzRWX"; //the desired folder id
var prps = [];
for(var prop in pickerBuilder){
var value = pickerBuilder[prop];
if(!!value){
if(typeof(value)==="object"){
var proto = value.__proto__; //jshint ignore: line
if(!!proto["addLabel"] && !!proto["addView"]){
prps.push(prop);
for(var key in value){
var target = value[key];
var type = Object.prototype.toString.call(target);
if(type === "[object Array]"){
prps.push(key);
for(var key in target[0]){
var value = target[0][key];
if(typeof(value)==="object"){
prps.push(key);
}
}
for(var key in target[0]){
var value = target[0][key];
if(typeof(value)==="string") {
prps.push(key);
}
}
}
}
}
}
}
}
var views = pickerBuilder[prps[0]][prps[1]];
for(var i=0; i<views.length; i++){
var view = views[i];
if(view[prps[3]] === "upload"){
view[prps[2]].parent = folderId;
}
}
EDIT 06/29/2020
There has been another change in the Drive picker API. To make this work please change what you have to:
var folderId = "10fYS3l32R6gk79POOSS8X_Vbsz7vqzRWX"; //the desired folder id
pickerBuilder.rw.kf["0"].Ta.parent = folderId;
EDIT 05/26/2020
There has been another change in the Drive picker API. To make this work please change what you have to:
var folderId = "10fYS3l32R6gk79POOSS8X_Vbsz7vqzRWX"; //the desired folder id
pickerBuilder.xw.jf["0"].Ta.parent = folderId;
EDIT 02/17/2020
There has been a change in the Drive picker API. To make this work please change what you have to:
var folderId = "10fYS3l32R6gk79POOSS8X_Vbsz7vqzRWX"; //the desired folder id
pickerBuilder.mw.$e["0"].Ra.parent = folderId;
To answer your questions directly:
1.) YES
2.) YES
Now, let's dig a little bit into what's happening under the hood. You are right:
My guess is that appmaker contstructs the first upload tab behind the curtains and there is no feature to disable this.
However, we can manipulate the object. So instead of creating a new picker view, let's simply configure the default one to upload the files to the folder you want. We can achieve that by doing the following:
1.) After you insert a Drive Picker into your UI, make sure the Drive Picker Properties are all empty:
2.) Next, go to the event handlers and click on the onPickerInit event handler. Type in this code:
var folderId = "10fYS3l32R6gk79POOSS8X_Vbsz7vqzRWX"; //the desired folder id
pickerBuilder.SW.Vq["0"].mc.parent = folderId;
In summary, I've come to the conclusion that the property SW contains the array of drive views, which are saved under the property Vq. Vq["0"] is the first view in the array of views and the mc property contains the features; hence parent = folderId.
I have created a grid in a doGet() function. This grid contains a dynamic number of rows choose by the user. In each rows i have some TextBox. I would like to be able to get all the data inserted by the user on the textboxes once the user clicked on the submit button.
So i give a dynamic ID to each textBox :
app.createTextBox().setName('gridText'+countRow);
countRow ++;
.........
My problem is that I dont know how to dynamicaly access to my TextBox on my submit function.
I tried something like this :
for( i = 0 ; i < countRow ; i++){
var buffer = e.parameter[e.parameter.gridText]+i;
.........
}
based on this : http://productforums.google.com/forum/#!category-topic/apps-script/services/nWN14AQ-9gQ
But it does'nt work... whereas this works :
var buffer = e.parameter.gridText3;
Any idea of what i'm doing wrong ?
Thanks for your help
Try
var buffer = e.parameter['gridText' + i.toString()];