I made a simple web app with GS and now need to optimize the speed of loading data from a Gsheet. Description of the app:
It is a tool to collect the correct colors of a product in an image.
When user clicks on load image button, the app will read the URL from a Gsheet where all the tasks are stored in rows and load the image. User then select colors from a palette and submit colors. Then the app will save the selected color values to the same row of the image URL.
Each task should be assigned to n users (no more no less, feedbackRequested as below) to avoid bias and the same task should not be assigned to the same user more than once. So I stored the tasks in this way:
index imageID URL feedbackRequested user rgb_value
1 104904677 *** 3
2 104904677 *** 3
3 104904677 *** 3
4 104904678 *** 2
5 104904678 *** 2
I spread n feedbackRequested to n rows to assign the tasks. So when a user clicks on load image, the following will be executed:
get an array with all imageIDs this user has done;
var user = Session.getActiveUser().getEmail();
var data = sheet.getRange(1,2,sheet.getLastRow(),4).getValues();
var filter_user = ArrayLib.filterByText(data, 3, user);
var user_imageIDs = filter_user.map(function(value,index) {return value[0]});
if imageID is in the array or user is not empty, continue looking until we find the row;
var ct = 1;
while ( (user_imageIDs.includes(data[ct][0])) || (data[ct][3] != '') ) {
ct++;
};
set the user value to the user on this row and save his answer to rgb_value.
sheet.getRange(ct+1, 5,1,2).setValues(user, somevalue);
This way it takes a lot of time to find the conditioned row with while loop. Is there other ways to do this?
I know this is a lot of information. Thanks very much in advance! Please let me know if you need the entire code to reproduce.
Create a Set of imageIDs which contain usr:
const imageSet = data.reduce((s,[id,_,_,usr])=>
usr === user ? s.add(id) : s,
new Set)
Use findIndex to find the index of data array, where usr is empty and id is not in imageSet:
const ct = data.findIndex(([id,_,_,usr])=> usr === "" && !imageSet.has(id))
Related
Test Sheet
So I'm trying to create sort of a database to update and store data. From what I can tell, I have to more than likely create a script to run when I need to transfer that data over, but I'm having a hard to trying to figure out where to even start.
The idea is on the 'Data Collection" tab you have the names, the section they are working on, and the units they made. Once I run a script, it would transfer the names and data to the "Section Data" tab and place them in Column A then place how many units they made in the proper section cell. It would then clear the Units field in "Data Collection" so it's ready for the next day.
Here is where it gets a bit complicated. If the name already exists in the "Section Data" tab, instead of adding a new row, add the number of units to the total that is already in the cell under that name.
So in the example above, running the script would make the "Section data" tab look like "Section Data After" as an example.
Any data entered in would either increment existing data if the name already exists. If it doesn't exist, it would create a new line in the first available slot and append it to the bottom of the existing data.
As an example:
So in the first tab
Bob in Section 1 doing 11 Units
Gina in Section 2 doing 5 Units
In the second tab, I have existing data
Bob in Section 1 did 6 Units and in Section 4 did 5 Units
Jeff in Section 3 did 8 Units
If I were to hit the transfer script button on the first tab, it would move the data over to the second tab like so
Bob in Section 1 now has 17 Units and Section 4 still have 5 Units as nothing was added to it
Jeff would still have only 8 Units in section 3 as there was no new data added for him
Gina would be added under Jeff with 5 Units in Section 2
Hopefully, I explained this well enough. Thank you.
Can anyone point me in the right direction to start?
Here is a script to complete what you are looking for:
/** #OnlyCurrentDoc */
function transfer() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Data Collection');
var data = ss.getSheetByName('Section Data');
var enteredNames = data.getRange('A:A').getValues();
var sections = data.getRange('A2:2').getValues();
var start = input.getRange('A3:A7').getRowIndex();
var end = start + input.getRange('A3:A7').getNumRows();
for(var i = start; i < end; i++) {
let name = input.getRange('A'+i).getValue();
if(name != '') {
let section = Number(input.getRange('B'+i).getValue());
let units = Number(input.getRange('C'+i).getValue());
var row = enteredNames.findIndex(row => row.includes(name))+1;
var col = sections[0].indexOf(section)+1;
if(row > 0) {
var oldUnits = data.getRange(row, col).getValue();
if(oldUnits != '') {units = Number(oldUnits)+units};
data.getRange(row,col).setValue(units);
} else {
if(units == 0) {units = ''};
let inputHere = data.getLastRow()+1;
data.getRange('A'+inputHere).setValue(name);
data.getRange(inputHere, col).setValue(units);
}
}
}
input.getRange('C3:C7').clearContent();
};
This function iterates through each name in the Collection sheet, pulls the relevant and related data (section, units), and searches the Data sheet to see if that name is already in there. If the name is, it then checks to see if there is a value in the section it's looking for, and if there is it adds it. Otherwise, it just places the number. If there is no name match in the Data sheet, it adds them to the end of the list (and inputs the correct units under the section).
Please let me know if you have any issues with this.
Below is a function you can add to the same script file, or a different one. It creates a menu at the top so you can easily run the transfer function.
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Functions')
.addItem('Transfer Data', 'transfer')
.addToUi();
}
Here is the link to the sheet where I tested this. It is a direct copy of the sheet you provided, the only difference being that the script is linked to it.
https://docs.google.com/spreadsheets/d/1vGV3sypyfMviyv9k2lQt4CGunDkzYf-4TX-bp8goKz4/edit?usp=sharing
If you would like me to further explain my code, or have any other questions, please let me know.
I am writing a form to update an inventory whenever an item is ordered. In this project, there is a google sheet which is linked to a form. The form contains a script that gets the current inventory from the sheet and subtracts one from this upon order.
The form has three questions: name, size, and comment.
The issue I am getting is that the system works once, and then somehow stores the first response for each successive form submission.
For a minimal working example: I've distilled the issue to the following code, which is triggered by a form submission:
function updateStock()
{
var customer_name = "";
const ss_id = ### I enter spreadsheet ID here ###
const form_id = ### I enter form ID here ###
var sheet = SpreadsheetApp.openById(ss_id);
var form = FormApp.openById(form_id);
var form_resp = form.getResponses()[0];
var customer_name = form_resp.getItemResponses()[0].getResponse();
Logger.log(customer_name);
// Rest of code follows from here
}
Upon entry for the first form I write:
Name: Peter
Size: M
Comment: none
Code returns:
1:43:40 AM Info Peter
(If I include the rest of the code, it correctly subtracts the inventory).
Then on the next (or tenth) submission, I might submit:
Name: Joe
Size: L
Comment: none
again, the code returns:
1:44:55 AM Info Peter
If I start from scratch or clear responses, I can get it to work once, but if not, the code will forever return "Peter" for the name, regardless of what I enter.
The form responses however are correct! It is just the call to
var form_resp = form.getResponses()[0];
var customer_name = form_resp.getItemResponses()[0].getResponse();
... that seems to have a "cached" value.
This seems like a weird bug, but am wondering if anyone has a clue as to what this might be (or, honestly, if there is a better way to achieve the same result!)
Thanks for taking the time.
Ugh. Very bad error on my part.
As vector pointed out:
The code: form.getResponses() returns all responses whereas I thought it was returning the current response (read the docs!)
Thus form.getResponses()[0] was naturally returning the first answer, whereas I should have written:
var N = form.getResponses().length-1;
var form_resp = form.getResponses()[N];
Whoops.
I have a Google form where some of the fields are "static" content, and some are populated by app script from a Google sheet which will be run on a daily basis. I nearly have it how I want it, but not quite.
I have a couple of dropdown lists that need to contain choices that navigate to different sections of the form, e.g:
Phill / Beacon Hill - Go to section called "Beacon Hill
Jane / Harbord - Go to section called "Harbord"
Fred / Beacon Hill - Go to section called "Beacon Hill"
etc...
What's happening is that instead of appending choices to the list, I'm overwriting which means I only end up with ONE choice in my dropdown, which is the last one added, i.e. Fred in the example above.
I've looked at lots of examples on the web but can't get this to work. I feel I'm very close but not quite there yet. Here's my code with a couple of lines that I believe are the problem. Can someone please tell me where I'm going wrong.
function populatePlayersClubsListV2() {
// ------------------------------------------------------------------------------------------------
// This gets each male player and the junior club they've been assigned to from the
// Junior_Clubs_Training_Sessions s/sheet (which is this s/sheet). It creates a list that the player
// chooses their name from and sets up a branch to the appropriate Club training sessions section
// in the form depending on which club they're assigned to. .
// ------------------------------------------------------------------------------------------------
// Open the "Find Junior Club Training Sessions" form
var form = FormApp.openById("1fo33ncgJY.................iRnMsERRTen8WjTH_xrx0");
// Identify the sheet in this spreadsheet holding the data needed to populate the drop-down. Here
// it's the "PlayersClubs" tab which says which club each player is assigned to.
var ss = SpreadsheetApp.getActive();
var playersClubsSheet = ss.getSheetByName("PlayersClubs");
// Grab the values from the rows & columns of the sheet - use 2 to skip header row. Use getMaxRows
// and getMaxColumns to avoid hard-coding the number of rows and columns.
var playersClubsSsValues = playersClubsSheet.getRange(2, 1, playersClubsSheet.getMaxRows() - 1, playersClubsSheet.getMaxColumns() - 1).getValues();
// We need to loop thro twice - once to populate the male players, and again for the female players.
// Males/females populate different fields and we hold the data-item-IDs of those fields in an array.
var formFieldsArray = [
["Male", 1397567108],
["Female", 1441402031]
];
for(var h = 0; h < formFieldsArray.length; h++) {
// Open the form field you want to populate - it must be a dropdown or multiple choice.
// Right-click field, inspect and look for data-item-ID followed by a number.
var playersClubsFormList = form.getItemById(formFieldsArray[h][1]).asListItem();
// Define array to hold values coming from the s/sheet and used to populate form fields.
var playersClubsArray = [];
var sectionMalePlayers = form.getItemById(309334479).asPageBreakItem();
var sectionFemalePlayers = form.getItemById(856495273).asPageBreakItem();
// Create the array of players and their clubs ignoring empty cells. Check if the s/sheet row
// matches male/female against formFieldsArray[h][0].
for(var i = 0, j = 0; i < playersClubsSsValues.length; i++) {
if(playersClubsSsValues[i][0] != "" && playersClubsSsValues[i][1] == formFieldsArray[h][0]) {
playersClubsArray[j] = playersClubsSsValues[i][0] + " - " + playersClubsSsValues[i][2];
if (formFieldsArray[h][0] = "Male") {
// ** THIS IS THE LINE THAT OVERWRITES BUT I NEED IT TO APPEND *** //
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
}
else {
// ** THIS IS THE LINE THAT OVERWRITES BUT I NEED IT TO APPEND *** //
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionFemalePlayers)]);
}
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
j = j + 1;
} // end if
} // end for loop
} // end for loop for Males/Females
}
Issue:
When setChoices is used, all choices that were previously stored in the item get removed. Only the ones that are specified when using setChoices get added to the item.
Right now, you are only specifying one choice:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
Solution:
You have to specify all choices you want the item to have. So in this case you would have to do the following:
Retrieve all choices that were previously stored via getChoices. This will retrieve an array with all current choices in the item.
Use Array.prototype.push() to add the choice you want to add to the list of choices.
When using setChoices, the array retrieved in step 1 and modified in step 2 should be provided as the argument.
Code sample:
Change this:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
For this:
var choices = playersClubsFromList.getChoices();
choices.push(playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers));
playersClubsFormList.setChoices(choices);
Note:
The same change would have to be made for all the lines where this problem is happening, like this one:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionFemalePlayers)]);
Reference:
getChoices()
setChoices(choices)
Full disclosure - not a programmer...I've just messed a good bit with Excel and Google Sheets, and am stuck trying to find a simple way to transform a specific set of data. Essentially, we have a website host who provides us with the ability to run an export of the results of various fillable forms. One of them is a registration for e-learning videos. The results of the form provide a specific url for each of about 20 videos we maintain...but neither the url or anything in the form itself automatically indicate a human readable label (like "Intro to Application Use") that's useful if someone wants to use the export track what customer has viewed what specific video. So, for each export I do of the data, I need to find a way to run a macro or script that will run through one column of the data, check it against a key that includes each video-specific url, and then spit out a user-readable name for each video into a second column.
So, I need a script that says, if A1:A100=a,b,c,d,e,f,g ("a,b,c,d,e,f,g" being any entry from a list or urls), then set B1:B100=a*,b*,c*,d*,e*,f*,g* ("a*,b*,c*,d*,e*,f*,g*" being the user readable name of each video represented by the urls).
Any thoughts on this? I think I have a way to do it within a formula in all cells in column B, but I'd be referencing so many lengthy urls in that single formula that it seems absurd not to handle it with a script. I'm just a deadbeat when it comes to scripting...
The Questioner is essentially looking for a means to match a meaningful Movie Title to an un-meaningful (but consistent) url provided by a web service. The Questioner says that they have 100 titles though we have no indication of the volume of transactions. Under the circumstances, and without knowing further volumes, the most efficient option is a linear search.
I created a spreadsheet with two sheets:
1 - Titles: Contains a list of the Supplier URL and the associated meaningful "Title Name". This is a sheet that would be maintained by the questioner as Titles are added or dropped.
2 - Transdata: Contains some same data; includes the Supplier URL, and a column set-aside for the meaningful Title name.
The script involves a nested loop. The first level goes through each row of transaction data. The second, nested loop, evaluates the url for each transaction row and returns the Title, which is saved to the sheet in the "MovieName" Column.
To make it easier to process the function, I've added an OnOpen function so that the Questioner can access the main menu to determine when they process.
function so_52892546() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Titles = ss.getSheetByName("Titles");
var Titlesrange = Titles.getDataRange();
var Titlesvalues = Titlesrange.getValues();
var TitleslastRow = Titles.getLastRow();
var Trans = ss.getSheetByName("TransData");
var Transrange = Trans.getDataRange();
var Transvalues = Transrange.getValues();
var TranslastRow = Trans.getLastRow();
for (var i = 1; i < TranslastRow; i++) {
for (var z = 1; z < TitleslastRow; z++) {
if (Transvalues[i][0] == Titlesvalues[z][0]) {
var Title = Titlesvalues[z][1];
//Logger.log("match i = "+1);
}
}
//Logger.log("i="+i+". Title: "+MovieTitle+", date = "+Transdata[i][1]+", income"+Moviesdata[i][2]);
var targetrange = Trans.getRange(i + 1, 4);
targetrange.setValue(Title);
}
}
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [{
name: 'Update Movie names',
functionName: 'so_52892546'
}, ];
spreadsheet.addMenu('Update Movies', menuItems);
Screenshot of the Titles sheet
"Before" and "After" screenshots of the Transdata sheet.
I am looking for a way for employees to send me an email or add information to a spreadsheet that will then add tasks to my task list. Ideally, the script would capture the task list, task, due date, and any notes.
I have already successfully implemented five scripts (five task lists) that allow my employees to add tasks to specific tasklists, following this script shown below. This works OK but does not have the capacity to add due dates or notes:
Automated email to task list API
I recently came across references to scripts that monitors task lists, and then posts them to a spread sheet, including task, due dates, notes, etc. It strikes me that a spreadsheet might be a better way to do this though it does not have the convenience of email:
Task list to spreadsheet API
I wonder if the REVERSE can be done. I envision a spreadsheet that I could give my employees access to, with two worksheets (NEW and PROCESSED) with columns:
TASKLIST TASK DUE DATE NOTES
and the script would run through this every hour or two. Anything in NEW would be processed and added to my task list, then moved to the end of PROCESSED.
Does anyone know of something like that out there? Alternatively, perhaps there are ways to change the email script so that it moves anything in the body of the email into the NOTES section of the task. I am a raw newbie at this BTW. Thanks.
you should replace
var newTask = Tasks.newTask().setTitle(title);
by
var newTask = Tasks.newTask().setTitle(title).setDue(date).setNotes(notes);
I'm also stuck in the way
I can from a spreadsheet :
- Create a new tasklist
- Create a new task in a dedicated tasklist (with due date and notes)
I can from the Gtasks :
- Check if the task is completed and mark it as completed in the spreadsheet
- Check if the task still exists in the spreadsheet and remove it if necessary
I'm still looking for a way to make a task completed in GTasks when it's closed in spreadsheet
All the functionality exists for you to accomplish this, but I don't know if there is a pre-built script out there that does what you want. You may want to look into use a Google Form that saves data to the spreadsheet, and then create a trigger for form submit that scoops up the data and creates a new task using it.
Is this [part] of what you're looking for?
https://developers.google.com/apps-script/articles/google_apis_reading_list
It syncs a Spreadsheet based task list with a your regular Task List, and if you mark the task done in gmail, it records that back in the spreadsheet.
// Fetch the list of URLs to keep synchronized
var articleUrls = SpreadsheetApp.getActiveSheet().getRange("A2:A");
for (var rowNum = 0; rowNum < articleUrls.getNumRows(); rowNum++) {
// Limit our range to a single cell containing a URL
var oneUrlCell = articleUrls.offset(rowNum, 0, 1, 1);
if (oneUrlCell.getComment() === "") {
// This is a new URL that needs to be shortened/inserted
var urlText = oneUrlCell.getValue();
if (urlText !== "") {
// Shorten the URL
Logger.log("Adding task for url: " + urlText);
var toShorten = UrlShortener.newUrl().setLongUrl(urlText);
var shortened = UrlShortener.Url.insert(toShorten);
// Insert the shortened URL into our reading list
var taskToInsert = Tasks.newTask().setTitle(shortened.getId());
taskToInsert.setNotes(urlText);
var newTask = Tasks.Tasks.insert(taskToInsert, readingListId);
// Save the new ID as our comment.
oneUrlCell.setComment(newTask.getId());
}
} else {
// This URL has already been inserted, update the status
var existingTask = Tasks.Tasks.get(readingListId, oneUrlCell.getComment());
if (existingTask.getStatus() === "completed") {
var absRowNum = oneUrlCell.getRow();
var completedCell = sheet.getRange(absRowNum, 2);
completedCell.setValue("Yes");
}
}
Should be part of the solution, no?
I'm looking to make something a bit bigger myself.