trouble using e.parameter in a script - google-apps-script

I have a script that is published as a service for a web app and i'm trying to build search functionality in it. the scipt is intended to allow the user to enter a term in a text box called searchBox, click search and show the row(s) of a spreadsheet containing that term.
function searchClickHandler(e) {
var app = UiApp.getActiveApplication();
var searchResultPanel = app.getElementById('searchResultPanel');
var searchResultPane = app.getElementById('searchResultPane');
var searchCloseButton = app.getElementById('searchCloseButton');
var searchTerm = e.parameter.searchBox;
if (searchTerm.toString().length == 6) {
var searchList = ArrayLib.filterByText(dataValues, 1, searchTerm.toString());
var searchTrim = new Array();
for (var i = 0; i < searchList.length; i++) {
var searchTrim1 = searchList[i].slice(1, 6);
var searchTrim2 = searchList[i].slice(8, 10);
var searchTrim3 = searchList[i].slice(17, 19);
searchTrim.push(searchTrim1.concat(searchTrim2,searchTrim3));
}
}
there are a few other else if's below that and then the handler should show the results but e.parameter.searchBox is coming back undefined. if i manually set searchTerm the script runs fine.
i am using e.parameter successfully in another handler in the same script so i am at a loss on this one.
thanks in advance.

The code you show is not really relevant to help... did you give a name to the "searchBox" when you created it or is it its ID ?
The e.parameter uses the name of the widget as reference.
EDIT (following comments): in the GUI builder try to add every callbackElement by their names (for widgets or ID for panels) separated by commas like this :

Related

Apps Script custom function working in script editor but not in Google Sheet custom function

I have built a simple custom function in Apps Script using URLFetchApp to get the follower count for TikTok accounts.
function tiktok_fans() {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
The Log comes back with the correct number for followers.
However, when I change the code to;
function tiktok_fans(handle) {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
and use it in a spreadsheet for example =tiktok_fans(A1), where A1 has #charlidamelio I get an #ERROR response in the cell
TypeError: Cannot read property '2' of null (line 6).
Why does it work in the logs but not in the spreadsheet?
--additional info--
Still getting the same error after testing #Tanaike answer below, "TypeError: Cannot read property '2' of null (line 6)."
Have mapped out manually to see the error, each time the below runs, a different log returns "null". I believe this is to do with the ContentText size/in the cache. I have tried utilising Utilities.sleep() in between functions with no luck, I still get null's.
code
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//tiktok urls
var qld = UrlFetchApp.fetch('https://www.tiktok.com/#thisisqueensland?lang=en').getContentText();
var nsw = UrlFetchApp.fetch('https://www.tiktok.com/#visitnsw?lang=en').getContentText();
var syd = UrlFetchApp.fetch('https://www.tiktok.com/#sydney?lang=en').getContentText();
var tas = UrlFetchApp.fetch('https://www.tiktok.com/#tasmania?lang=en').getContentText();
var nt = UrlFetchApp.fetch('https://www.tiktok.com/#ntaustralia?lang=en').getContentText();
var nz = UrlFetchApp.fetch('https://www.tiktok.com/#purenz?lang=en').getContentText();
var aus = UrlFetchApp.fetch('https://www.tiktok.com/#australia?lang=en').getContentText();
var vic = UrlFetchApp.fetch('https://www.tiktok.com/#visitmelbourne?lang=en').getContentText();
//find folowers with regex
var match_qld = raw_data.exec(qld);
var match_nsw = raw_data.exec(nsw);
var match_syd = raw_data.exec(syd);
var match_tas = raw_data.exec(tas);
var match_nt = raw_data.exec(nt);
var match_nz = raw_data.exec(nz);
var match_aus = raw_data.exec(aus);
var match_vic = raw_data.exec(vic);
Logger.log(match_qld);
Logger.log(match_nsw);
Logger.log(match_syd);
Logger.log(match_tas);
Logger.log(match_nt);
Logger.log(match_nz);
Logger.log(match_aus);
Logger.log(match_vic);
Issue:
From your situation, I remembered that the request of UrlFetchApp with the custom function is different from the request of UrlFetchApp with the script editor. So I thought that the reason for your issue might be related to this thread. https://stackoverflow.com/a/63024816 In your situation, your situation seems to be the opposite of this thread. But, it is considered that this issue is due to the specification of the site.
In order to check this difference, I checked the file size of the retrieved HTML data.
The file size of HTML data retrieved by UrlFetchApp executing with the script editor is 518k bytes.
The file size of HTML data retrieved by UrlFetchApp executing with the custom function is 9k bytes.
It seems that the request of UrlFetchApp executing with the custom function is the same as that of UrlFetchApp executing withWeb Apps. The data of 9k bytes are retrieved by using this.
From the above result, it is found that the retrieved HTML is different between the script editor and the custom function. Namely, the HTML data retrieved by the custom function doesn't include the regex of ("followerCount":)([0-9]+). By this, such an error occurs. I thought that this might be the reason for your issue.
Workaround:
When I tested your situation with Web Apps and triggers, the same issue occurs. By this, in the current stage, I thought that the method for automatically executing the script might not be able to be used. So, as a workaround, how about using a button and the custom menu? When the script is run by the button and the custom menu, the script works. It seems that this method is the same as that of the script editor.
The sample script is as follows.
Sample script:
Before you run the script, please set range. For example, please assign this function to a button on Spreadsheet. When you click the button, the script is run. In this sample, it supposes that the values like #charlidamelio are put to the column "A".
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
return [match_text[2]];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
When this script is run, the values are retrieved from the URL and put to the column "B".
Note:
This is a simple script. So please modify it for your actual situation.
Reference:
Related thread.
UrlFetchApp request fails in Menu Functions but not in Custom Functions (connecting to external REST API)
Added:
About the following additional question,
whilst this works for 1 TikTok handle, when trying to run a list of multiple it fails each time, with the error TypeError: Cannot read property '2' of null. After doing some investigating and manually mapping out 8 handles, I can see that each time it runs, it returns "null" for one or more of the web_content variables. Is there a way to slow the script down/run each UrlFetchApp one at a time to ensure each returns content?
i've tried this and still getting an error. Have tried up to 10000ms. I've added some more detail to the original question, hope this makes sense as to the error. It is always in a different log that I get nulls, hence why I think it's a timing or cache issue.
In this case, how about the following sample script?
Sample script:
In this sample script, when the value cannot be retrieved from the URL, the value is tried to retrieve again as the retry. This sample script uses the 2 times as the retry. So when the value cannot be retrieved by 2 retries, the empty value is returned.
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
if (!match_text || match_text.length != 3) {
var retry = 2; // Number of retry.
for (var i = 0; i < retry; i++) {
Utilities.sleep(3000);
web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
match_text = raw_data.exec(web_content);
if (match_text || match_text.length == 3) break;
}
}
return [match_text && match_text.length == 3 ? match_text[2] : ""];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
Please adjust the value of retry and Utilities.sleep(3000).
This works for me as a Custom Function:
function MYFUNK(n=2) {
const url = 'my website url'
const re = new RegExp(`<p id="un${n}.*\/p>`,'g')
const r = UrlFetchApp.fetch(url).getContentText();
const v = r.match(re);
Logger.log(v);
return v;
}
I used my own website and I have several paragraphs with ids from un1 to un7 and I'm taking the value of A1 for the only parameter. It returns the correct string each time I change it.

How to assign Go_To_Page in Form Service on Multiple Choice?

Just starting to use the Forms Service in Google Apps Script. Need to direct the form to take the user to a specific page depending on the answer that is given. Here's my current code
form.addMultipleChoiceItem()
.setTitle('What would you like to do?')
.setRequired(true)
.setChoiceValues(['Request a new reservation.','Change the date or number of tickets for an existing reservation.'])
Now, I've found this section in the documentation: Enum PageNavicationType
But they don't example the use of Go_To_Page. Also the creation of the ChoiceValues is wonky to me.
Anyone out there worked this out?
Instead of .setChoiceValues you want to use .setChoices([arrayOfChoices]), and use the .createChoice(value, page) to create the choice.
Edit: Updated code to fix errors
function createAMCQuestion(){
var af = FormApp.getActiveForm();
var pRed = af.getItemById("1300443051").asPageBreakItem(); //use findPageIds to get the page id of a pre-created page
var pGreen = af.getItemById("902629766").asPageBreakItem();
var item = af.addMultipleChoiceItem().setTitle('Pic a color?'); // creates the Multiple Choice Question
af.moveItem(item.getIndex(), 1); // Makes the question show up as the second question in the form (starts at 0)
//create choices as an array
var choices = [];
choices.push(item.createChoice('Red', pRed));
choices.push(item.createChoice('Green', pGreen));
// assignes the choices for the question
item.setChoices(choices);
}
function findPageIds(){
var af = FormApp.getActiveForm();
var pages = af.getItems(FormApp.ItemType.PAGE_BREAK);
for (i in pages){
var pName = pages[i].getTitle();
var pId = pages[i].getId();
Logger.log(pName+":"+pId);
}
}

An alternative way to pass data (eg, array values) through handler without using ScriptProperties

I built an app in which I use ScriptProperties to store data from a handler to its but(e) function. This was working nice, until other people started using the same spreadsheet at the same time. So often happens that one person is taking a time thinking about what item choose from a checkbox menu and another person uses the same function, changing the data stored at scriptProperties and affecting the use of the function by the first person.
What is the best way to fix it, using an alternative way to pass information through the handler?
Here one sample of one of theese functions (in which I'm using ScriptProperties to pass the values ofletterSpreadsheetId and recipientArray):
function letter(letterSpreadsheetId){
ScriptProperties.setProperty('letterSpreadsheetId', letterSpreadsheetId); // different people may have different letterSpreadsheetId;
ScriptProperties.setProperty('letter', 1); // to be used in another function
var activeSheet = ss.getActiveSheet();
var app = UiApp.createApplication().setHeight(400).setWidth(600);
var panel = app.createVerticalPanel(); // you can embed that in a form panel
var label = app.createLabel("Choose a receiver").setStyleAttribute("fontSize", 18);
app.add(label);
var sheet = SpreadsheetApp.openById(letterSpreadsheetId).getSheetByName("receivers");
var recipientArray = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var item3Panel = app.createHorizontalPanel();
item3Panel.add(app.createLabel("receiver"));
var listBox = app.createListBox().setName('item3');
for(var i = 0; i < (recipientArray.length); i++){
listBox.addItem(recipientArray[i][1]);
}
item3Panel.add(listBox);
var recipientArrayStr = JSON.stringify(recipientArray);
ScriptProperties.setProperty('recipientArr', recipientArrayStr);
var handlerBut = app.createServerHandler("butAnswerLetter").addCallbackElement(panel);
var but = app.createButton("submit").setId("submitButton2").addClickHandler(handlerBut);
panel.add(item1Panel)
.add(item2Panel)
.add(item3Panel)
.add(but)
.add(app.createLabel().setId("answer"));
var scroll = app.createScrollPanel().setPixelSize(600, 400).setTitle("My title 1");
scroll.add(panel);
app.add(scroll);
ss.show(app);
}
function butAnswerLetter(e){
var letterSpreadsheetId = ScriptProperties.getProperty('letterSpreadsheetId');
var recipient = e.parameter.item3;
ScriptProperties.setProperty('recipient', recipient);
var recipientArrayRecovery = ScriptProperties.getProperty('recipientArr');
var recipientArray = JSON.parse(recipientArrayRecovery);
for(var i=0;i<recipientArray.length;i++){
if(recipient == recipientArray[i][1]){
var usedRecipientArray = recipientArray[i];
}
}
You have 2 possibilities (that I know), either use userProperties instead of script-Properties as these are associated with the user but it will require the user to login and authorize, or - and this will work in every case even if the app is accessed anonymously, use the tags that you can write on almost any widget.
the syntax is quite simple, here is a small code example :
function doGet(){
var app = UiApp.createApplication().setTitle('test_TAG');
var list = app.createListBox(true).setVisibleItemCount(5).setPixelSize(30,450).setName('list');
var handler = app.createServerHandler('show').addCallbackElement(list);
list.addChangeHandler(handler);
var data = [];
for(var n = 0;n<20;n++){
list.addItem(n+' ');
data.push('available value = '+Number(n+1));
}
list.setTag(data.toString());
app.add(list);
return app
}
function show(e){
var app = UiApp.getActiveApplication();
var data = e.parameter.list_tag.split()
var selected = e.parameter.list;
app.add(app.createTextBox().setText(selected).setPixelSize(200,20));
app.add(app.createTextArea().setText(data.join()).setPixelSize(200,300));
return app;
}
testable here
Edit
following Zig's pertinent comment :
I forgot to mention the hidden widget (or a textBox / area set to invisible, useful for debugging when you want to check what it contains ! ) that is also useable of course...
The comment about a user having multiple windows showing the same app is also worth mentioning !
All in all you have 3 possibilities after all !
(thanks to Zig Mandel)

Have a listbox populate with every folder in myDrive

I am trying to define where my file is to be saved to. I am starting out at the end user and moving inwards, so that means the start of the UI. I have the Label and the listbox but I am having troubles populating the listbox with the folders and sub folders that are in my Google Drive. The end product will be: Click the drop down menu -> chose folder to save file into -> click "submit" and it saves it into the folder.
Here is what i have so far: (Don't mind all my notes. I have zero JS experience and no programming experience so i am learning from the code that i already have from the original template maker. This is all of the code if you need it: http://pastebin.com/rbvu5Pie )
//look here for code about the listbox to show folders
grid.setWidget(2, 0, app.createLabel('Folder:')); //makes the label "folder" next to the listbox
var list = app.createListBox(); //defines what to do when i say list
grid.setWidget(2, 1, list); //puts the listbox to the right of the label
var folder = DocsList.getAllFolders()[0]; //defines that when i say "folder" it is supposed to get all folders
for (var i = 0; i < folder.length; i++) {
list.addItem(folder[i].getName(),folder[i].getId())
}
//this is the end of the code for the listbox showing folders
Thanks for your help everyone, i really appreciate it!
Here is a test code that seems to work. I didn't test it thoroughly but it seems to do what it has to do...
I left the textBox with ID visible to make it easier to debug but you should set it invisible in the final code.
There are probably a few improvements to add but it gives the general idea...
function doGet(){
var app = UiApp.createApplication();
var curFN = app.createTextBox().setText('MyDrive/').setName('curFN').setId('curFN').setWidth('400');
var curFID = app.createTextBox().setText('x').setName('curFID').setId('curFID').setWidth('400');
var list = app.createListBox().setName('list').setId('list').addItem('please select a folder','x');
var grid = app.createGrid(3,2).setText(0,0,'Choose a folder in your drive').setWidget(0,1,curFN).setWidget(2,1,curFID).setWidget(1,1,list);
var folders = DocsList.getRootFolder().getFolders();
for (var i = 0; i < folders.length; i++) {
list.addItem(folders[i].getName(),folders[i].getId())
}
var handler = app.createServerHandler('folderSelect').addCallbackElement(grid);
list.addChangeHandler(handler);
app.add(grid);
return app;
}
function folderSelect(e){
var app = UiApp.getActiveApplication();
var currentFN = e.parameter.curFN;
var currentFID = e.parameter.list;
Logger.log(currentFID);
var list = app.getElementById('list');
var curFN = app.getElementById('curFN');
var curFID = app.getElementById('curFID');
if(currentFID=='x'){currentFID=DocsList.getRootFolder().getId() ; curFN.setText('MyDrive/')};
var startFolder = DocsList.getFolderById(currentFID);
var folders = startFolder.getFolders();
list.clear().addItem('no other subFolder','x').addItem('Go back to Root','x');
if(folders.length>0){list.clear(); list.addItem('please select a subFolder','x')};
for (var i = 0; i < folders.length; i++) {
list.addItem(folders[i].getName(),folders[i].getId())
}
curFN.setText(currentFN+DocsList.getFolderById(currentFID).getName()+'/');
if(currentFID==DocsList.getRootFolder().getId()){curFN.setText('MyDrive/')};
curFID.setText(currentFID);
return app;
}
App online here, needs authorization for Docslist Service

How to make a listBox trigger a handler when there is only one item?

Here is a script that demonstrate the issue I have (test online here)
var lbArray1 = ['item1', 'item2', 'item3', 'item4', 'item5'];
function doGet(e){
var app = UiApp.createApplication();
var vPanel = app.createVerticalPanel().setStyleAttribute('padding','20px')
app.add(vPanel);
var lb1 = app.createListBox().setName('lb1').setId('lb1').setVisibleItemCount(1);
var lb2 = app.createListBox().setName('lb2').setId('lb2').setVisibleItemCount(4);
var lb3 = app.createListBox().setName('lb3').setId('lb3').setVisibleItemCount(1);
lb3.addItem(lbArray1[0]);
for(var i in lbArray1){
lb1.addItem(lbArray1[i]);
lb2.addItem(lbArray1[i]);
}
var msg = app.createLabel('waiting for trigger').setId('msg');
vPanel.add(lb1).add(lb2).add(lb3).add(msg);
var Handler = app.createServerHandler('test').addCallbackElement(vPanel);
lb1.addChangeHandler(Handler);
lb2.addClickHandler(Handler);
lb3.addClickHandler(Handler);// I tried different trigger modes without success
return app;
}
function test(e){
var app = UiApp.getActiveApplication();
var msg = app.getElementById('msg');
msg.setText('triggered by '+e.parameter.source)
return app;
}
ListBox 3 has only one item and shows only one item (I have this situation in an app that uses a popup to show folders content while adapting the list size to its content, sometimes I can have just 1 file in a folder)
ListBox 3 never triggers the handler unless I change the setVisibleItemCount to 2 or more...
the code I use to adapt the list size goes simply like that :
...
ODlist.setVisibleItemCount(numItem > 6 ? 6 : numItem > 1 ? numItem : 2)
...
and I really would prefer to set the last number to 1, it would be looking so nice ;-) but I can't .
Any workaround idea ?
EDIT : for now I found that using lb3.addMouseOverHandler(Handler); is a useable solution but this handler causes some issues when more than one item is visible... I'd rather find something more elegant.
I understand if this isn't want you want, but it was my solution to the problem. I just simply added a list item that was a user prompt like: "Select a Spreadsheet". From my code:
var files = DocsList.getFolder("Incoming Product Data").getFiles();
fileChooser.addItem("Select a Spreadsheet");
for (var i = 0; i < files.length; i++) {
fileChooser.addItem(files[i].getId());
The only other thing I had to do was then build an if statement into the trigger, so that if someone select a file, but then switches back to the prompt "Select a Spreadsheet" that it doesn't throw an error by attempting to handle the prompt as an actual fileId.
Best I could do.