Retrieving a full row of google sheets data to JavaScript - javascript

I am trying to build a form that can recall data based on a specific number being inputted using Google Scripts and JavaScript. When a number is inputted the JavaScript should call the Google Scripts so that it can locate the values I need based on the number. For example bellow is a google sheets. I want when the user inputs a number it searches for that value in column C and then grabs the data from that row. Ex. if the number is 14 then values: 2021-05-12, 5, 6 and 7 are returned to the JavaScript.
UPDATED:
Everything I want is working however, when I try to retrieve the date from the array in the JavaScript it does not work. The numbers are the only thing functioning. I know the date is in the array as I can see it in the logs.
JavaScript:
function callDataRetriever(){
var number = document.getElementById("number").value;
google.script.run.withSuccessHandler(dataRetriever).retreiveData(number);
}
function dataRetriever(data){
document.getElementById("location").value = data[0]; //This works
document.getElementById("dateOpened").value = data[1]; //This does not work. Stops the function from continuing its task.
document.getElementById("value1").value = data[2]; //Without the date everything here down works
document.getElementById("value2").value = data[3];
document.getElementById("value2").value = data[4];
document.getElementById("value4").value = data[5];
//...
}
Google Scripts (I have 28 total values) Everything here works perfectly as seen in the logs bellow.
function retreiveData(number){
var url = "urlToSpreadsheet";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
var data = ws.getRange(1,1, ws.getLastRow(), ws.getLastColumn()).getValues();
var dataValues = [];
var filterData = data.filter(
function(r){
if(r[2] == number){
var i = 3;
while(i < 29){
dataValues.push(r[i]);
i++;
}
}
}
)
return dataValues;
}
I am not sure if the problem lies in the way the date is formatted.
Thank you!

You can use filter to perform the operation you want
var filterData = data.filter(
function(r){
//Select the index init in 0 in your case is 2
return r[0] == 'YOUR_SEARCH_VALUE'
}
)
// Use your filter data
ui.alert(filterData[0]) //data[row][column]

I believe your goal as follows.
When number is given and run the function retreiveData, you want to search the value of number from the cells "C4:C" of "Data" sheet, and want to retrieve the values of the columns "D", "E" and "F" for the searched row.
In this case, I would like to propose to use TextFinder. When the TextFinder is used, searching value is run in the internal server side. By this, the process cost will be a bit low. Ref
When this is reflected to your script, it becomes as follows.
Modified script:
In this case, please modify retreiveData at Google Apps Script side as follows.
function retreiveData(number){
var url = "linkToSpreadsheet";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
var data = ws.getRange("C4:C" + ws.getLastRow()).createTextFinder(number).matchEntireCell(true).findNext();
if (data) {
return data.offset(0, 1, 1, 4).getValues()[0];
}
return Array(4).fill("No value");
}
And, please modify dataRetriever at Javascript side as follows.
function dataRetriever(data){
console.log(data) // Here, you can check the value from `retreiveData` at the console.
data.forEach((v, i) => {
document.getElementById(`value${i + 1}`).value = v;
});
}
Note:
In this case, when the search value of number is not found, No value is put to the input tags of value1 to value4. When you want to modify this, please modify return Array(4).fill("No value"); for your actual situation.
References:
Benchmark: Process Costs for Searching Values in Spreadsheet using Google Apps Script
Class TextFinder

There are two ways:
var data = [
[1, 55, 5545, 54, 51],
[2, 45, 541, 848, 1215],
[3, 323, 3232, 215, 3051],
[4, 13, 5151, 1513, 2315]
]
number = 3;
// via object where the 'numbers' are keys
var data_obj = Object.fromEntries(data.map(d => [d[0], d.slice(1,)]));
console.log(data_obj[number]); // [ 323, 3232, 215, 3051 ]
// via filter (about the same as #macorreag's answer)
var res = data.filter(d => d[0] == number)[0].slice(1,);
console.log(res); // [ 323, 3232, 215, 3051 ]
An object makes sense in the cases when you need to extract info from the same data several times. For single requests a filter looks better, I think.
And if the numbers always start from 1 and go sequentially 2, 3, 4, 5, ... etc, the simpliest solution is an array and its indexes:
var data = [
[1, 55, 5545, 54, 51],
[2, 45, 541, 848, 1215],
[3, 323, 3232, 215, 3051],
[4, 13, 5151, 1513, 2315]
]
var number = 3;
var data_array = data.map(d => d.slice(1,));
console.log(data_array[number-1]); // [ 323, 3232, 215, 3051 ]
In this case you don't even need the first column 'numbers' at all. You can just extract the next four columns and get the rows by indexes (-1): number 1 is data[0], number 2 is data[1] etc.

Related

Rerun a function while a list still has elements

I think this may be a fairly trivial issue but I am still quite new to Node and Javascript.
I have a list and a function as follows:
var my_list = [1, 7, 9, 112, 15, 17, 19, 25, ...]
// Main search function
const search = async(_) => {
// Loop from ID 1 to X
for (let index = 1; index < 10000; index++) {
if (my_index.indexOf(index) !== -1) {
// Try to find a brewery with this ID
const brewery = await findBreweriesById(index);
// If it's found ..
if (brewery) {
console.log(brewery.brewery_name);
}
} else {
console.log(`Nothing found for ID ${index}..`);
}
}
console.log("Finished searching!");
};
search();
The function is going to be running as a call to a rate limited API which means not all values in my_list will be fetched at once. I have to repeat the fetching function. What I want to do is to be able to remove any successfully fetched value from the main list, i.e. my_list and rerun the function until all values in my_list have been fetched.
I know two things,
I need to remove the fetched index element from the list. I have figured out I could do this using my_list.splice(indexOf(index),1)
I know I can use a while loop but I have no idea where exactly I'd put it and what condition to set. This also confuses me as to where exactly then I would be splicing to remove the element.
Any help would be appreciated.
EDIT
To be more specific, the API call is rate limited to 100 calls per minute. After that it returns a 429 Too many requests. So I want to remove all the 200 returns and recycle, i.e. rerun the function for the 429 which should remain in the list.
Maybe try this:
var my_list = [ 1, 7, 9, 112, 15, 17, 19, 25 ]
// Main search function
const search = async _ => {
while( my_list.length > 0 ){
const index = my_list[my_list.length - 1]
// Try to find a brewery with this ID
const brewery = await findBreweriesById(index);
if (brewery) {
my_list.splice(indexOf(index),1)
console.log(brewery.brewery_name);
} else {
console.log(`Nothing found for ID ${index}..`);
}
}
}
search();

createTextFinder always finds a match in the first row when it shouldn't

I am having a problem with the createTextFinder function. It always finds a match in the first row of the sheet when it should not.
What I am trying to do is to find out if the data in an array is in the sheet and if so, copy the row with the match to another sheet and delete it from the original. However, the most important thing is that I only need to transfer the first match, i.e. if there are two elements in the sheet that appear in the search array, I only have to delete one of them and transfer them once from the original sheet to the new one.
The code I am running is:
function moveData(){
let dataToSearch = [22, 10, 12];
const date = new Date();
dataToSearch.forEach(data => {
let dataFinder = sheet.createTextFinder(data.toString());
let result = dataFinder.findAll();
if(result.length>0){
let matchValues = sheet.getRange(result[0].getRow(), 1, 1, 5).getValues();
for (let value in matchValues){
found.appendRow([date, "user", matchValues[value][0], matchValues[value][1], matchValues[value][2], matchValues[value][3], matchValues[value][4]]);
sheet.deleteRow(result[0].getRow());
}
}
})
}
This would be the sheet without executing the code:
Expected output:
As you can see in the image, only the first entries of id 22, 10 and 12 have been removed, leaving the rest on the sheet.
Any idea why it takes the first element of the sheet even though it is not in the array?
Explanation:
When I execute your code, I get the expected output as shown in your screenshot.
However, since you want to remove the first match only, there is no need of using that inner for (let value in matchValues) loop, since you only want to delete/move one match only.
Also it is not a good idea to use appendRow iteratively but instead append the data to an array and then use setValues to copy the data.
Modified Script:
function moveData(){
let dataToSearch = [22, 10, 12];
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('Sheet1'); // change that to your case
const found = ss.getSheetByName('found'); // change that to your case
const date = new Date();
const found_data = [];
dataToSearch.forEach(data => {
let dataFinder = sheet.createTextFinder(data.toString());
let result = dataFinder.findAll();
if(result.length>0){
let matchValues = sheet.getRange(result[0].getRow(), 1, 1, 5).getValues();
found_data.push([date, "user", ...matchValues.flat()]);
sheet.deleteRow(result[0].getRow());
}
});
found.getRange(found.getLastRow()+1,1,found_data.length,found_data[0].length).setValues(found_data);
}

Why does function A work and function B throw an error?

I have 2 functions, one works and the other does not - the reason the second function is more important is that essentially I want to be able to change what's in the final array. Deconstruction is great and all but I don't expect to have the same incoming data landing in the same place - elsewhere I fixed this issue by creating individual arrays and just having all my functions return data by index, but I'm a little bamboozled here.
Heres the first one (createLogs) that does what I want it to do and if the data always came, in the same way, would do more than enough.
function createLogs(array,runtimeArray) {
let basicsLog=[]
array.map(function callback(currentValue,index) {
let currentRuntime = runtimeArray[index]
let [,date,time,systemMode,systemSetting,calEvent,ProgramMode,coolSetTemp,heatSetTemp,currentTemp] = currentValue
basicsLog.push([index,date,time,systemMode,systemSetting,calEvent,ProgramMode,coolSetTemp,heatSetTemp,currentTemp,currentRuntime])
})
console.log(basicsLog)
return basicsLog
}
Returns
[ 36,
2018-11-19T05:00:00.000Z,
'second failure',
'heat',
'heatOff',
'hold',
'Sleep',
70,
70,
69.5,
135 ],
[ 37,
2018-11-19T05:00:00.000Z,
'2:35:00',
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
null ] ]
Now here is createResults() - I think much better for a situation where what comes in is highly variable (basically its a table , and the columns in the table are different in different situations)
createResults() looks like this:
function createResults(originalThermostatArray,dateArray,timeArray, systemSettingsArray, systemModeArray, calendarEventArray, programModeArray, coolSetTempArray, heatSetTempArray, currentTempArray, thermostatTempArray, outdoorTempArray, combinedRuntimesArray) {
let resultArray = []
originalThermostatArray.forEach(function(currentValue,index) {
let currentDate = dateArray[index]
let currentTime = timeArray[index]
let currentSystemSetting = systemSettingsArray[index]
let currentSystemMode = systemModeArray[index]
let currentCalEvent = calendarEventArray[index]
let currentProgramMode = programModeArray[index]
let currentCoolSetTemp = coolSetTempArray[index]
let currentHeatSetTemp = heatSetTempArray[index]
let currentAvgTemp = currentTempArray[index]
let currentThermostatTemp = thermostatTempArray[index]
let currentOutdoorTemp = outdoorTempArray[index]
let currentRuntime = combinedRuntimesArray[index]
resultArray.push([index, currentDate, currentTime, currentSystemSetting, currentSystemMode, currentCalEvent,currentProgramMode, currentCoolSetTemp, currentHeatSetTemp, currentAvgTemp, currentThermostatTemp, currentOutdoorTemp, currentRuntime])
})
console.log(resultArray)
return resultArray
}
And Returns
[ 36,
2018-11-19T05:00:00.000Z,
'second failure',
'heat',
'heatOff',
'hold',
'Sleep',
70,
70,
69.5,
69.5,
23.5,
135 ],
[ 37,
2018-11-19T05:00:00.000Z,
'2:35:00',
null,
null,
null,
null,
null,
null,
null,
null,
23.5,
null ] ]
On the front end the code looks like this
div.resultSummaries.equipmentSummary
h1.summaryHeader.equipmentSummaryHead Possible Equipment Failures
h3.equipmentSummaryBody #{equipresultCount}
p.equipmentSummaryBody This table looks for increasing thermostat temperatures during a cooling call.
table.fullResults.equipmentResultsTable
caption.equipTitle Possible Equipment Failures Log
thead
tr
th.resultHeader.equipmentHeader Index
th.resultHeader.equipmentHeader Date
th.resultHeader.equipmentHeader Time
th.resultHeader.equipmentHeader System Setting
th.resultHeader.equipmentHeader System Mode
th.resultHeader.equipmentHeader Calendar Event
th.resultHeader.equipmentHeader Program Mode
th.resultHeader.equipmentHeader Cool Set Temp
th.resultHeader.equipmentHeader Heat Set Temp
th.resultHeader.equipmentHeader Avg Temp
th.resultHeader.equipmentHeader Thermostat Temp
th.resultHeader.equipmentHeader Outdoor Temp
th.resultHeader.equipmentHeader Runtime(sec)
tr
tbody
- let equipFailsResultLog = result.equipLogs;
for item in equipFailsResultLog
tr.equipRow
for item in equipArray
td.resultTable.result.equipResult #{item}
tfoot
But createResults() returns this error on the front end:
/views/upload.pug:137 135| for item in equipFailsResultLog 136| tr.equipRow > 137| for item in equipArray 138| td.resultTable.result.equipResult #{item} 139| tfoot 140| Cannot read property 'length' of undefined.
I think the fact that they return differently is causing the error but why? note that I tried writing createResults() as a for each and a map
function1 (createlogs) allows me to get into the array and access the individual elements and place them correctly
function2 (createResults) does not..
How can I access the nested elements using function 2 or display them the way I would want to?
added:
create results is called like this
const basicLogsArray = returnLogic.createResult(originalThermostatArray,dateArray,timeArray,systemSettingsArray,systemModeArray,calendarEventArray,programModeArray,coolSetTempArray,heatSetTempArray,currentTempArray,thermostatTempArray,outdoorTempArray,combinedRuntimesArray)
create log is called similarly but with just array and runtime array.
const equipFailsLogs = reportLogic.rowsByIndex(
basicLogsArray,
possibleEquipmentFailuresArray
)
console.log(equipFailsLogs)
This is what goes to the front end. (i run another function to get the whole row each bit of date is associated with and return it as an html table in pug.
return {
"Results": {
"coolCount":coolFailsRows.length/2 , coolLogs:coolFailsLogs,
"heatCount":heatFailsRows.length/2 , heatLogs:heatFailsLogs,
"miscCount":miscFailsRows.length/2 , miscLogs:miscfailsLogs,
"equipCount":equipFailsLogs.length/4 , equipLogs:equipFailsLogs,
}};
}
exports.runThermostat = runThermostat

Find inside 2 dimensional Array Using Google Apps Script

I have values in an array which are:
[8:00 AM, 9:00 AM] //result of another function
And I would like to find their values inside this 2-Dimensional Array:
[
[8:00 AM],
[9:00 AM],
[10:00 AM],
[11:00 AM],
[12:00 NN],
[1:00 PM],
[2:00 PM],
[3:00 PM]
]
and change the their values "Not Available"
The array log are the values from google sheet using this code:
function testRow(){
var lookDate = "Aug 28, 2019";
var ss = SpreadsheetApp.openByUrl(url);
var ts = ss.getSheetByName("Time_Select");
var checkData = ts.getRange(1, 1, 1, ts.getLastColumn()).getDisplayValues()[0];
var index = checkData.indexOf(lookDate)+1;
var timeValues = ts.getRange(2, index, ts.getLastRow()-1, 1).getValues();
Logger.log(timeValues)
/*var checkSplit = dateValues.join().split(",");
var checkMe = checkSplit.indexOf(dataDisable[1]);
var timeValues = ts.getRange(checkMe, index).getValue();*/
}
I tried to use this code (as you can see in the above google script code):
var checkSplit = dateValues.join().split(",");
var checkMe = checkSplit.indexOf(dataDisable[1]);
var timeValues = ts.getRange(checkMe, index).getValue();
but it was giving me a wrong value. Do you have any suggestions or solutions on how can I search the values inside the 2 dimensional array then go to its location in google sheet and change its value to "Not Available"? Thank you very much in advance for the help.
here is the link for my google sheet:
https://docs.google.com/spreadsheets/d/1lEfzjG1zzJVPMN8r-OpeZm6q9_IqSwk9DNCEY-q7ozY/edit?usp=sharing
The output from your timeValues are strings. If the contents of [8:00 AM, 9:00 AM] are also strings, this is how you can compare them and set the rescpective cell values to "Not Available" in case of coincidence:
function testRow(){
var lookDate = "Aug 28, 2019";
var ss = SpreadsheetApp.openByUrl(url);
var ts = ss.getSheetByName("Time_Select");
var checkData = ts.getRange(1, 1, 1, ts.getLastColumn()).getDisplayValues()[0];
var index = checkData.indexOf(lookDate)+1;
// I called your array resulting from another function 'times'
var times=['8:00 AM', '9:00 AM'];
var timeValues = ts.getRange(2, index, ts.getLastRow()-1, 1).getValues();
for(var i=0;i<times.length;i++){
for(var j=0;j<timeValues.length;j++){
if(times[i]==timeValues[j][0]){
timeValues[j][0]="Not Available"
}
}
}
ts.getRange(2, index, ts.getLastRow()-1, 1).setValues(timeValues);
}
Basically, you loop through both arrays and when the contents coincide you replace the respective entry of timeValues with "Not Available". After exiting the loops you assign to your range the values of the updated timeValues array.
How about:
var result = timeValues.map((_tv) => { return [_tv]; });

Reassign / Update Variables

I've tried searching all over the web but could only find how to reassign cell values.
A little background for my code:
The input sheet contains data in columns A through U, but columns R and S are purposefully empty. I need to set & sort the Data Range from columns A through Q ONLY, and the number of rows may change every time I run the code (so the row index needs to be dynamic). When I run my code, the variable does not update, and I don't get any errors. Why is the code not updating my variable for input data. Any help would be greatly appreciated!
Thanks!
function SortUpdate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName("Input");
var inputData = input.getDataRange();
var lastRow = inputData.getNumRows();
inputData = input.getRange(2, 1, lastRow, 17);
for(var i = 9; i<=(15);i++){
inputData.sort({column: i, ascending: false});
}
}
So far you have assigned two different ranges to inputData but you have not collected any data. Take a look at the range.getValues() function here.
function SortUpdate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName("Input");
var inputData = input.getDataRange();//This returns a range not data
var lastRow = inputData.getNumRows();
inputData = input.getRange(2, 1, lastRow, 17);//The inputData variable has received two assignments which one do you want? Both are ranges and do not have any data.
for(var i = 9; i<=(15);i++){
inputData.sort({column: i, ascending: false});
}
}
It would be helpful to see some of your data.
I looked at the range.sort command and some of the examples and I'm wondering if you don't think that it was intended to be used like this:
inputData.sort([{column: 9 ascending: false},{column: 10, ascending: false},{column: 11, ascending: false},{column: 12 , ascending: false},{column: 13, ascending: false},{column: 14, ascending: false},{column: 15, ascending: false}]);
I put the command into the function like below. You won't be able to get this to run because I'm using some of my own utilities to store a global variable in an array in a file so that I can toggle the ascending argument between true and false every time I run it. But this is what it looks like and it definitely is sorting columns A through Q not including the header row.
function SortUpdate()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName("Input");
var inputData = input.getDataRange();//This returns a range not data
var lastRow = inputData.getNumRows();
inputData = input.getRange(2, 1, lastRow, 17);//The inputData variable has received two assignments which one do you want?
var dirA=myUtilities.getArrayByName('Params');
inputData.sort([{column: 9, ascending: dirA.state=='true'},{column: 10, ascending: dirA.state=='true'},{column: 11, ascending: dirA.state=='true'},{column: 12 , ascending: dirA.state=='true'},{column: 13, ascending: dirA.state=='true'},{column: 14, ascending: dirA.state=='true'},{column: 15, ascending: dirA.state=='true'}]);
dirA['state'] = (dirA['state']=='true')?false:true;
myUtilities.saveArray('Params', 'DICT', dirA);
}
Instead of using the sheet.getDataRange() and sheet.getNumRows(), you can use the sheet.getLastRow() method, who will return the value of the last row use on the sheet. More about the getLastRow method : https://developers.google.com/apps-script/reference/spreadsheet/sheet#getLastRow()
For the sort method part, you can use the code write by Cooper which is working, or send us a exemple of what kind of sort method you want.

Categories

Resources