Code shortening for data entry form Google App script - google-apps-script

I'm new to coding, and I know I am going a long way about this and making my script run slow, but I can't figure out how to shorten and optimise it (now I have tried to map the second bit of code using Marios comment)
I have made a data entry form on Google Sheets for athletes I coach to use as a training diary. After recording training data in a session, they hit the save button and this script transfers it to a different spreadsheet with all of their training data ever in.
Below is a section of code I have attempted to shorten with Marios comment:
function submitSession1() {
workloadSubmit();
myValue();
}
function workloadSubmit(){
var inputSS = SpreadsheetApp.getActiveSpreadsheet();
var inputS = inputSS.getSheetByName("Session 1");
var outputSS = SpreadsheetApp.openByUrl()
var workloadS = outputSS.getSheetByName();
var dtCurrentTime = new Date();
//Input Values for Workload data
var workloads = [[inputS.getRange("M1").getValue(),
inputS.getRange("N1").getValue(),
inputS.getRange("O1").getValue(),
inputS.getRange("P1").getValue(),
inputS.getRange("AK3").getValue(),
inputS.getRange("AK5").getValue(),
inputS.getRange("AL3").getValue(),
inputS.getRange("AL5").getValue(),
inputS.getRange("BC3").getValue(),
inputS.getRange("BC5").getValue(),
inputS.getRange("BD3").getValue(),
inputS.getRange("BD5").getValue(),
inputS.getRange("AM3").getValue(),
inputS.getRange("AM5").getValue(),
inputS.getRange("AN3").getValue(),
inputS.getRange("AN5").getValue(),
inputS.getRange("AO3").getValue(),
inputS.getRange("AO5").getValue(),
inputS.getRange("AP3").getValue(),
inputS.getRange("AP5").getValue(),
inputS.getRange("AQ3").getValue(),
inputS.getRange("AQ5").getValue(),
inputS.getRange("AR3").getValue(),
inputS.getRange("AR5").getValue(),
inputS.getRange("AS3").getValue(),
inputS.getRange("AS5").getValue(),
inputS.getRange("AT3").getValue(),
inputS.getRange("AT5").getValue(),
inputS.getRange("AU3").getValue(),
inputS.getRange("AU5").getValue(),
inputS.getRange("AV3").getValue(),
inputS.getRange("AV5").getValue(),
inputS.getRange("AW3").getValue(),
inputS.getRange("AW5").getValue(),
inputS.getRange("AX3").getValue(),
inputS.getRange("AX5").getValue(),
inputS.getRange("AY3").getValue(),
inputS.getRange("AY5").getValue(),
inputS.getRange("AZ3").getValue(),
inputS.getRange("AZ5").getValue(),
inputS.getRange("BA3").getValue(),
inputS.getRange("BA5").getValue(),
inputS.getRange("BB3").getValue(),
inputS.getRange("BB5").getValue(),
dtCurrentTime]];
workloadS.getRange(workloadS.getLastRow()+1, 1, 1,
45).setValues(workloads);
}
// Drills Data Submit
function myValue(col) {
var inputSS = SpreadsheetApp.getActiveSpreadsheet();
var inputS = inputSS.getSheetByName("Session 1");
var outputSS = SpreadsheetApp.openByUrl()
var drillsS = outputSS.getSheetByName("Drills Data");
var dtCurrentTime = new Date();
return inputS.getRange(col).getValue();
}
var colns = ["M1", "N1", "O1", "P1", "A14","B14","D14","F14","G14","H14","J14","K14","L14","M14","N14","O14","P14","Q14","R14","S14","T14","U14"];
var drillsData = colns.map(myValue)
drillsData.push(dtCurrentTime)
I am now getting the error code:
Exception: Argument cannot be null: a1Notation (line 71, file "Code")Dismiss
Any help is much appreciated

You can calculate drillsData using maps:
function myValue(col) {
return inputS.getRange(col).getValue();
}
var colns= ["M1", "N1", "O1", "P1", "A14","B14","D14","F14","G14","H14","J14",
"K14","L14","M14","N14","O14","P14","Q14","R14","S14","T14","U14"];
var drillsData = colns.map(myValue)
drillsData.push(dtCurrentTime)
*Don't forget to call drillsData as [drillsData].
Unfortunately, the columns you want to retrieve are not sequential, therefore selecting the full range is not an option.
Or you can create custom functions to make your code look cleaner:
function importSheets(sheetN) {
return outputSS.getSheetByName(sheetN);
}
var workloadS = importSheets("W.L + Full Routine Data")
For the latter you can again create maps using the same logic described for one.
As a result, you can have a collection of sheets objects as elements in an array and call by using their index.

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.

Is GAS/google sheet an alternative option to PHP/mySQL?

I keep finding forum results that refer to the google visualiser for displaying my query results. But it just seems to dump the data in a pre-made table. I haven't found a way to write my own custom table dynamically.
In the past, when I have hacked together PHP to make use of mySQL DB, I would simply connect to my DB, run a query into an array, then cycle through the array and write the table in HTML. I could do IF statements in the middle for formatting or extra tweaks to the displayed data, etc. But I am struggling to find documentation on how to do something similar in a google script. What is the equivalent work flow? Can someone point me to a tutorial that will start me down this path?
I just want a simple HTML page with text box and submit button that runs a query on my Google sheet (back in the .gs file) and displays the results in a table back on the HTML page.
Maybe my understanding that GAS/google sheets is an alternative to PHP/mySQL is where I'm going wrong? Am I trying to make a smoothie with a toaster instead of a blender?
Any help appreciated
Welcome David. Ruben is right, it's cool to post up some code you have tried. But I spent many months getting my head around Apps-script and love to share what I know. There are several ways to get the data out of a Google sheet. There is a very well document Spreadsheet Service For GAS. There is also a client API..
There is the approach you mention. I suggest converting the incoming data to JSON so you can do with it as you like.
Unauthenticated frontend queries are also possible. Which needs the spreadsheet to be published and set to anyone with the link can view, and uses the Google visualisation API
var sql = 'SELECT A,B,C,D,E,F,G where A = true order by A DESC LIMIT 10 offset '
var queryString = encodeURIComponent(sql);
var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/'+ spreadsheetId +'/gviz/tq?tq=' + queryString);
query.send(handleSampleDataQueryResponse);
function handleSampleDataQueryResponseTotal(responsetotal) {
var myData = responsetotal.getDataTable();
var myObject = JSON.parse(myData.toJSON());
console.log(myObject)
}
Other Approaches
In your GAS back end this can get all of your data in columns A to C as an array of arrays ([[row1],[row2],[row3]]).
function getData(query){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A1:C');
var data = range.getValues();
// return data after some query
}
In your GAS front end this can call to your backend.
var data = google.script.run.withSuccessHandler(success).getData(query);
var success = (e) => {
console.log(e)
}
In your GAS backend this can add data to your sheet. The client side API will also add data, but is more complex and needs authentication.
function getData(data){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A1:C');
var data = range.setValues([[row1],[row2],[row3]]);
return 'success!'
}
In your GAS frontend this can send data to your backend.
var updateData = [[row1],[row2],[row3]]
var data = google.script.run.withSuccessHandler(success).getData(updateData);
var success = (e) => {
console.log(e)
}
Finally
Or you can get everything from the sheet as JSON and do the query in the client. This works okay if you manipulate the data in the sheet as you will need it. This also needs the spreadsheet to be published and set to anyone with the link can view.
var firstSheet = function(){
var spreadsheetID = "SOME_ID";
var url = "https://spreadsheets.google.com/feeds/list/" + spreadsheetID +"/1/public/values?alt=json";
return new Promise((resolve,reject)=>{
$.getJSON(url, (data)=>{
let result = data.feed.entry
resolve(result)
});
})
}
firstSheet().then(function(data){
console.log(data)
})

Google Script, Run functions in sequence without exceeding execution time

I have a lot of functions that fetch JSON API data from a website, but if I run them in sequence in this way, I get the exceeding execution time error:
function fetchdata () {
data1();
data2();
data3();
data4();
...
}
I can schedule a trigger to run them at 5 minutes one of the other (cause a single one runs in 3 minutes), but I would like to know if there is any other way around. Thank you
EDIT:
Every "data" function is like this one:
function data1() {
var addresses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Import");
var baseUrl = 'https://myapiurl';
var address = addresses.getRange(2, 1, 500).getValues();
for(var i=0;i<address.length;i++){
var addrID = address[i][0];
var url = baseUrl.concat(addrID);
var responseAPI = UrlFetchApp.fetch(url);
var json = JSON.parse(responseAPI.getContentText());
var data = [[json.result]];
var dataRange = addresses.getRange(i+2, 2).setValue(data);
}
}
data2 is for rows 502-1001,
data3 is for rows 1002-1501,
and so on...
I just removed the concat because it has performance issues according to MDN but obviously the real problem is the fetch and there's not much we can do about that unless you can get your external api to dump a bigger batch.
You could initiate each function from a webapp and then have it return via withSuccessHandler and then start the next script in the series and daisy chain your way through all of the subfunctions until your done. Each sub function will take it's 3 minutes or so but you only have to worry about keeping each sub function under 6 minutes that way.
function data1()
{
var addresses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Import");
var baseUrl = 'https://myapiurl';
var address = addresses.getRange(2, 1, 500).getValues();
for(var i=0;i<address.length;i++){
var responseAPI = UrlFetchApp.fetch(baseUrl + address[i][0]);
var json = JSON.parse(responseAPI.getContentText());
var data = [[json.result]];
var dataRange = addresses.getRange(i+2, 2).setValue(data);
}
}

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)

Instant search using TextBox and onKeyUp and onKeyDown

I would like to make Google Apps Script webapp that search spreadsheet instantly as you type in the TextBox.
The implementation I"m using now is following. For each key up event new search is lauched with the current search string. I'm using delay on the function that onKeyUp is calling and if the onKeyDown is called I try to kill the previous function call and just go with the new. This works sometimes but the search takes different time to complete so the last one to finish is not allways the right one.
I don't know how to solve this task in Google Apps script. This can be implemented using something like this but these functions are not awailable:
var timer;
function up(){
setTimeout(mySpreadsheetSearchFunction, 500);
}
function down(){
clearTimeout (timer);
}
This is the code of the current implementation that is not working correctly:
function up(){
var cache = CacheService.getPrivateCache();
var now = parseInt(cache.get('iterate'));
Utilities.sleep(500);
if(parseInt(cache.get('iterate')) !== parseInt(now)){
return;
}else{
search();
}
showInGui();
}
function down(){
var cache = CacheService.getPrivateCache();
cache.put('iterate', 1+parseInt(cache.get('iterate')));
}
The CacheService is probably wrong tool for this job, what could be beter? Is this the right way to implement something like this?
Not sure you have to use the cache feature to get a fast result... I made such a script that works pretty well, I use variants of it all the time ;-)
you can test it on this test sheet
here is how it works, the script is maybe quite long but look only at the handler part (click)
On other version I read the spreadsheet outside of the function so the array of data becomes a global variable and mostly to limit the number of spreadsheet calls (it can hit the quota limit in this version sometimes)
// G. Variables
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lastrow = ss.getLastRow();
// ...
function onOpen() {
var menuEntries = [ {name: "Search GUI", functionName: "searchUI"},
];
ss.addMenu("Search Utilities",menuEntries);// custom menu
}
// Build a simple UI to enter search item and show results + activate result's row
function searchUI() {
var app = UiApp.createApplication().setHeight(130).setWidth(400);
app.setTitle("Search by name / lastname / adress");
var panel = app.createVerticalPanel();
var txtBox = app.createTextBox().setFocus(true);
var label=app.createLabel(" Item to search for :")
panel.add(label);
txtBox.setId("item").setName("item");
var label0=app.createLabel("Row").setWidth("40");
var label1=app.createLabel("Name").setWidth("120");
var label2=app.createLabel("Lastname").setWidth("120");
var label3=app.createLabel("Street").setWidth("120");
var hpanel = app.createHorizontalPanel();
hpanel.add(label0).add(label1).add(label2).add(label3)
//
var txt0=app.createTextBox().setId("lab0").setName("0").setWidth("40");
var txt1=app.createTextBox().setId("lab1").setName("txt1").setWidth("120");
var txt2=app.createTextBox().setId("lab2").setName("txt2").setWidth("120");
var txt3=app.createTextBox().setId("lab3").setName("txt3").setWidth("120");
var hpanel2 = app.createHorizontalPanel();
hpanel2.add(txt0).add(txt1).add(txt2).add(txt3)
var hidden = app.createHidden().setName("hidden").setId("hidden");
var subbtn = app.createButton("next ?").setId("next").setWidth("250");
panel.add(txtBox);
panel.add(subbtn);
panel.add(hidden);
panel.add(hpanel);
panel.add(hpanel2);
var keyHandler = app.createServerHandler("click");
txtBox.addKeyUpHandler(keyHandler)
keyHandler.addCallbackElement(panel);
//
var submitHandler = app.createServerHandler("next");
subbtn.addClickHandler(submitHandler);
submitHandler.addCallbackElement(panel);
//
app.add(panel);
ss.show(app);
}
//
function click(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("next ?")
var txt0=app.getElementById("lab0").setText('--');
var txt1=app.getElementById("lab1").setText('no match').setStyleAttribute("background", "white");// default value to start with
var txt2=app.getElementById("lab2").setText('');
var txt3=app.getElementById("lab3").setText('');
var item=e.parameter.item.toLowerCase(); // item to search for
var hidden=app.getElementById("hidden")
var data = sh.getRange(2,2,lastrow,3).getValues();// get the 3 columns of data
for(nn=0;nn<data.length;++nn){ ;// iterate trough
Logger.log(data[nn])
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 3 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}
function next(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("no other match")
var hidden=app.getElementById("hidden");
var start=Number(e.parameter.hidden)+1;//returns the last search index stored in the UI
var item=e.parameter.item.toLowerCase(); // item to search for
var txt0=app.getElementById("lab0");
var txt1=app.getElementById("lab1").setStyleAttribute("background", "yellow");
var txt2=app.getElementById("lab2");
var txt3=app.getElementById("lab3");
var data = sh.getRange(2,2,lastrow,3).getValues();// get the 3 columns of data
for(nn=start;nn<data.length;++nn){ ;// iterate trough
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 3 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}
Using the HtmlService you can instead implement this in pure HTML and JavaScript. This gives you the ability to load the spreadsheet data once, and then do the search client-side, where the performance should be better.