I'm supposed to create a calcExp function (calculate Expenses) but the code I've written isn't qualifying with the online case problem.
Create the calcExp() function. The purpose of this function is to calculate the row and column totals from the travelExp table. Add the following commands to the function:
Create the expTable variable referencing all the table row (tr) elements within the table body of the travelExp table.
Loop through the rows in the expTable collection and, for each table row, set the value of the input element with the ID subtotalIndex to the value returned by the calcClass() function using the parameter value dateIndex. Where Index is the value of the index counter in the for loop. Format the value returned by the calcClass() function as a text string using the formatNumber() function to 2 decimals.
After the for loop, set the values of the transTotal, lodgeTotal, mealTotal, and otherTotal input elements by calling the calcClass() function using parameter values "trans", "lodge", "meal", and "other". Format the values using the formatNumber() to 2 decimal places.
Set the value of the expTotal input element to the value returned by the calcClass() function using "sum" as the parameter value. Format the returned value using the formatUSCurrency() function.
function calcExp() {
var expTable = document.querySelectorAll("table#travelExp tr");
for (var i = 0; i < expTable.length; i++) {
document.getElementById("subtotal"+i).value = formatNumber(calcClass(date[i]), 2);
}
document.getElementById("transTotal").value = formatNumber(calcClass("trans"), 2);
document.getElementById("lodgeTotal").value = formatNumber(calcClass("lodge"), 2);
document.getElementById("mealTotal").value = formatNumber(calcClass("meal"), 2);
document.getElementById("otherTotal").value = formatNumber(calcClass("other"), 2);
document.getElementById("expTotal").value = formatUSCurrency(calcClass("sum"));
}
calcClass succeed for me. It is:
function calcClass(sumClass) {
var sumFields = document.querySelectorAll("."+sumClass);
var sumTotal = 0;
for (var i = 0; i < sumFields.length; i++) {
var itemValue = parseFloat(sumFields[i].value);
if (!isNaN(itemValue)) {sumTotal += itemValue;}
}
return sumTotal;
}
In case you're wondering, formatNumber and formatUSCurrency are functions that come with the exercise. They are:
function formatNumber(val, decimals) {
return val.toLocaleString(undefined, {minimumFractionDigits: decimals, maximumFractionDigits: decimals});
function formatUSCurrency(val) {
return val.toLocaleString('en-US', {style: "currency", currency: "USD"} );
My error message:
calcExp()
1) calcExp() sets correct subtotal and total values
2) calcExp() sets correct subtotal and total values
0 passing (228ms)
2 failing
1) calcExp()
calcExp() sets correct subtotal and total values:
ReferenceError: date is not defined
at calcExp (dl_expenses.js:51:74)
at Context. (nt-test-7dd2e611.js:26:5)
2) calcExp()
calcExp() sets correct subtotal and total values:
ReferenceError: date is not defined
at calcExp (dl_expenses.js:51:74)
at Context. (nt-test-7dd2e611.js:48:5)
Related
I am having one google sheet having more than 100 rows with column of "NAME, PLACE, PHONE". I want to change /correct the phone number on specific person Ex.John in the side bar (Form.html) and the correct place & phone number to be edit in that specific row of my google sheet "Phonelist". The code.gs given below which is not working. Could you lease rectify the same?
function sidebar() {
var html = HtmlService.createHtmlOutputFromFile("Form").setTitle('Phone Details');
SpreadsheetApp.getUi().sidebar(html);
}
function result(form) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("Phonelist");
var data = ws.getDataRange().getValues();
var name = form.name;
var place = form.place;
var phone = form.phone;
for (var i = 1; i < data.length; i++) {
if(data[i][1] == "John"){
var result = [name,place,phone];
ws.getRange(dat[i]).setValue(result);
}
}
}
It is difficult to understand what you exactly need. But there are some issues which are visible.
ws.getRange(data[i])is not valid. See docs. You need a row and a column at least, and in your case also the number of columns since your are inserting a range. Currently you only have a column. The solution is `
const startColumn = 1 // start at column A
const numberOfRows = 1 // update one row at a time
const numberOfColumns = result.length // this will be 3
ws.getRange(data[i], startColumn, numberOfRows, result.length)
.setValues(result) // setValues is correct, setValue is incorrect
The second issue is that you said that NAME is in the first column, but your test is testing against the second column. Array start at 0, i.e. the first item is actual accessed by [0]. therefore your test if(data[i][1] == "John") actually checks if the second column PLACE is equal to "John". To fix this, replace the [1] with [0], so:
if(data[i][0] == "John")
The third issue is handled in the first answer. You are using setValue() which is only to be used to set one cell. But since you are setting a number of cells at one time, you should use setValues() instead.
I am new to coding.
I am trying to edit and set values from an entire row but want to skip certain columns, because there are formulas in it.
In short: I want to / have to keep track of sing-in and sign-out times, which then will be calculated in the spreadsheet but shouldn't be overwritten by the array. Is there a way to skip every 3rd "value"/index (as these are the columns which have the formulas)?
In fact I want to skip the columns: TOTAL, day1tot, day2tot, day3tot .... day14tot.
function editCustomerByID(id,customerInfo){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("DATA");
const custIds = ws.getRange(2, 1, ws.getLastRow()-1, 1).getDisplayValues().map(r => r[0].toString().toLowerCase());
const posIndex = custIds.indexOf(id.toString().toLowerCase());
const rowNumber = posIndex === -1 ? 0 : posIndex +2;
Logger.log(customerInfo);
ws.getRange(rowNumber, 2, 1, 8).setValues([[
customerInfo.name,
customerInfo.total,
customerInfo.day1in,
customerInfo.day1out,
customerInfo.day1tot,
customerInfo.day2in,
customerInfo.day2out,
customerInfo.day2tot
// UNTIL DAY 14
]]);
return true;
To skip columns, you can subdivide your range into individual ranges and implement conditional statements
Sample:
function editCustomerByID(id,customerInfo){
...
var valueArray = [
customerInfo.name,
customerInfo.total,
customerInfo.day1in,
customerInfo.day1out,
customerInfo.day1tot,
customerInfo.day2in,
customerInfo.day2out,
customerInfo.day2tot
// UNTIL DAY 14
]
var startColumn = 2;
//loop through all values
for (var i = 0; i < valueArray; i++){
// filter out every 3rd value
if((i+1) % 3 != 0){
ws.getRange(rowNumber, (startColumn + i)).setValue(valueArray[i]);
}
}
return true;
Note that the sample code above uses the method setValue() instead of setValues()
Performing multiple requests to set values to individual ranges is less efficient that setting all values at once within a single request, however in your case it is necessary since your desired value range is not continuos
I have a set of documents in Firestore in this format. Questions array will 10 questions.
I want to get the data of questions field: one row for one question
I do I code in the appscript to perform this
This is my code so far (for one document only)
function test(){
const firestore = getFirestore();
var query = firestore.getDocument("QuestionCollection/test").fields;
var data = {};
data.subject = query.subject;
data.questions= query.questions;
const sheet = SpreadsheetApp.getActiveSheet();
for(i = 0 ; i < 10 (no. of question); i++){
const row = [data.questions[i].answer, data.questions[i].difficulty];
sheet.appendRow(row);
}
}
Error:
TypeError: Cannot read property 'answer' of undefined
Modification points:
When I saw your sample data in the image, it seems that the array length of questions is 2. But at the for loop, the end index of loop is 9. I think that by them, such error occurs.
When you want to put the value of "Serial", it is required to add the value for putting to Spreadsheet.
In your script, appendRow is used in a loop. In this case, the process cost becomes high.
When above points are reflected to your script, it becomes as follows.
Modified script:
Your for loop is modified as follows.
From:
for(i = 0 ; i < 10 (no. of question); i++){
const row = [data.questions[i].answer, data.questions[i].difficulty];
sheet.appendRow(row);
}
To:
var values = [];
for (var i = 0; i < data.questions.length; i++) {
const row = [i + 1, data.questions[i].answer, data.questions[i].difficulty];
values.push(row);
}
sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
For the end index of loop, the array length is used.
Reference:
setValues(values)
You shouldn't query the .fields property directly (because your data isn't converted properly). Assuming you're using v28+ of the library, your code should look something like this:
function test(){
const firestore = getFirestore();
const query = firestore.getDocument("QuestionCollection/test").obj; // Don't use .fields here
const sheet = SpreadsheetApp.getActiveSheet();
const values = [["Serial", "Answer", "Difficulty"]]; // Init 2D array with Header row
// Loop through each question in the array and extract necessary values to construct Data rows
for (const question of query.questions){
values.push([query.questions.indexOf(question) + 1, question.answer, question.difficulty]);
}
// Replace 1, 1 below with coords of "Serial" header cell
const range = sheet.getRange(1, 1, values.length, values[0].length);
range.setValues(values);
// sheet.getRange(subjRow, subjCol).setValue(query.subject); // Add location for Subject data
}
I saw that you wanted "Serial" to represent "Question number", so I added that column to the header and data rows.
As Tanaike mentioned, there's a huge performance hit for writing to the spreadsheet in a loop, so it's better if you set up a 2D array of values to write all at once using range.setValues(array2D). Ideally, you'll want to minimize the calls to the Spreadsheet API.
Disclaimer: I'm an active contributor to the FirestoreGoogleAppsScript library.
I am writing a script that updates a cell in a Google Sheet based on the intersection of a Row and Column. I find the row by iterating through a list of unique teacher names. When I find the name, I capture its row number in the variable "row". I then iterate through a range of column headers that are dates to find the specific date, and capture its column number as the variable "column". However, when I look at the structure of each object in my code, the "names" object appears as [[Person1], [Person2],..., [PersonX]] whereas the "dates" object appears as [[date1, date2,..., dateX]]. I can iterate through the names object just fine, but the dates object, not so much, and I suspect it is due to the structure.
I understand that the getDisplayValues returns a string and it works fine in another area of my code when I need to grab the date from a cell and name it "dateValue". But when I look for that dateValue in the "dates" object in the code below, that is where my code fails.
Here is a sample of the code:
function updateTracker(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var teacherName = sheet.getRange('F7').getDisplayValue();
var dateValue = sheet.getRange('N7').getDisplayValue();
var tracker = SpreadsheetApp.openById('AAAbbbCCCxxxYYYzzz111222333');
var tab = tracker.getSheetByName('Tracker');
var names = tab.getRange(1, 4, tab.getLastRow(), 1).getDisplayValues();
var dates = tab.getRange(1, 1, 1, tab.getLastColumn()).getDisplayValues();
for (var i in names) {
if (names[i][0] === teacherName) {
var row = parseInt(i+1);
}
}
for (var j in dates){
if (dates[0][j] === dateValue) {
var column = parseInt(j+1);
}
}
var cell = tab.getRange(row, column).setValue('x');
}
I get an error on that last line that getRange expects (number, number) but it getting (number, null).
Any suggestions on editing the code?
Logic:
In case of [[Person1], [Person2],..., [PersonX]], You iterate the "outer" array. There are X elements in the outer array. Each element itself is a array("inner") like [Person1] with only 1 primitive element each lime Person1.
Whereas the "dates" object appears as [[date1, date2,..., dateX]]. There is only 1 element in the outer array[date1, date2,..., dateX] and this array contains many elements like date1.
Solution:
You should iterate the inner array in the dates array:
for (var j in dates[0]){//note `[0]`
Using for...in to iterate arrays is also considered bad practice. Use for...of instead:
let column = null, j = 0;
for (const date of dates[0]){
j++;
if (date === dateValue) {
column = parseInt(j+1);//also bad to declare var in a block. Moved declaration outside
}
}
References:
What does the range method getValues() return and setValues() accept?
Why is using "for...in" for array iteration a bad idea?
for...of -MDN reference
I'm trying to place a data into a specific location in an array using array.splice(). So that I can get data from sheet1 to the main sheet in the right column.
Here's how it looks so far. (The Code actually returns correctly at Logger.log)
var header1 = data1[0]; //Header of Sheet1
var header2 = data2[0]; //Header of sheet2
var newData = new Array(44); //There are 45 columns
for (i in data1) {
if (i > 0) { //Take Row by Row Except Header of Sheet1
var row = data1[i];
if (row != "") {
for (j in data2) { //Searching Through All Rows of Sheet2
var row2 = data2[j];
if (row[0] == row2[4]) { //If Data In Column Match That Row, Proceed
//Getting the Right Index of SameName Column
for (i in header2) {
var col = header2[i];
for (j in header1) {
var col2 = header1[j];
if (col == col2) {
Logger.log(j+" "+row2[i]);
newData.splice(j,0,row2[i]);
}
}
}
}
}
}
}
It Returns Correctly 1 Line before:
Logger.log(j+" "+row2[i]);
42 Timestamp
3 Name
4 lastName
2 DoB
0 ID . .. 1 Type //All data returns correctly with the correct index (and none is null)
Here's The Issue:
Logger.log(newData);
[ID, Type, null, null, DoB, null, ...., TimeStamp, null, null]
DoB had index at 2 in previous line at Logger.log, but somehow the array has null at pos2.
Also... newData.length increases from 44 to 68
Somehow the index and data got mixed up later in the array.
Thank you in advance to all of you.
If you log your newly-instantiated array, you will see that, by default, all array elements are assigned a value of 'null'. In JavaScript, arrays are dynamic, which means their size is not pre-determined.
Assuming that for some rows the condition 'if (row[0] == row2[4])' evaluates to 'false', some indices will be skipped. Naturally, it will produce null elements in your fixed-size array.
Use array literal notation and Array.prototype.push() method to add new elements:
var newData = [];
newData.push(element);
Also, you shouldn't be using the 'for in' loop for iterating over arrays. Using it is precisely why you don't see 'null' values being logged. More details here Why is using "for...in" with array iteration a bad idea?