how to insert a row in the middle of the table. google sheets API - google-sheets-api

I can't figure out how to add a row to a certain place always adds to the end of the table.
{
SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum valueInputOption = SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum.RAW;
SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum insertDataOption = SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum.INSERTROWS;
ValueRange requestBody = new ValueRange();
requestBody.Range = range + "!A4:A";
IList<Object> obj = new List<Object>();
obj.Add("A2");
obj.Add("B2");
IList<IList<Object>> values = new List<IList<Object>>();
values.Add(obj);
requestBody.Values = values;
SpreadsheetsResource.ValuesResource.AppendRequest request = service.Spreadsheets.Values.Append(requestBody, spreadsheetId, range+"!A4:A");
request.ValueInputOption = valueInputOption;
request.InsertDataOption = insertDataOption;
AppendValuesResponse response = request.Execute();
}```

As you can see in the official documentation, values appended via spreadsheets.values.append are appended "after the last row of the table". With append, you cannot decide where exactly you want to add your data. The API looks for a "table" based on the input range you specify, as explained here.
If you want to add a row in the exact place you want, you should first (1) insert an empty range and then (2) write your desired values to the empty range. You would have to do two successive requests:
In order to insert the empty range where you want to append your data to, use the method spreadsheets.batchUpdate and in the field requests, provide an insertRange request. Specify the row and column indexes you want to insert your range to (field range) and whether the previously existing cells should be shifted down or right (field shiftDimension).
Second, once you have empty cells where you want your values to be, you can use spreadsheets.values.batchUpdate to write those values (defined in the field data). Check this guide to writing values for a detailed explanation.
Reference:
Appending values
Method: spreadsheets.batchUpdate
InsertRangeRequest
Method: spreadsheets.values.batchUpdate
Writing

Related

Dynamic Office Script date for modified row

I am trying to get the date a row was modified inserted into a Modified Date column for that particular row using Office Scripts for Excel Online.
This is the closest I can get since I haven't found scripts that that get just the modified row which will need to be dynamic. Details are in the comments in the code below:
function main(workbook: ExcelScript.Workbook) {
// This finds the location of the cell at K2 (Modified Date column is on K:K) but actually
//want to get the modified row location.
let datetimeRange = workbook.getWorksheet("Form1").getRange("K2");
// Get dynamic range of the worksheet. I'm not sure if this will work since there might be
//some blanks. The number of rows and possibly columns will change so needs to be dynamic.
let myRange =
workbook.getWorksheet("Form1").getRange("A1").getSurroundingRegion().getAddress();
// Get the current date and time with the JavaScript Date object.
let date = new Date(Date.now());
// This adds the date string to K2 but actually only want it added to Modified Date in the
//Modified Date column (dynamic since columns can change) for only the row that was modified.
datetimeRange.setValue(date.toLocaleDateString() + ' ' + date.toLocaleTimeString());
}
I know this is an old thread, but for the others that may come here looking for a solution, this one is macro-less:
Turn on iterative calculations for formulas (Options -> Formulas).
Add this formula in K2: =IF(CELL("row")=ROW(K2), NOW(), K2)
Drag that down your K column and you're done!

Reference another cell in the same row having calculated the minimum value in a datatable column

Using VB, I've used the following line to successfully find the minimum value in a specific column (say column 5, where the values are all of double) in a datatable:
Dim test as double
test = datatable.Compute("min(sourcecolumn)", "")
I would now like to refer to the values in other columns (let's say column 2) along the row containing that minimum column value.
Any help would be much appreciated as I can't get my head round it!
Thanks
You can use the DataTable.Select() method to get the row(s) that contain the minimum value. DataTable.Select() returns a DataRow(). In the code below, I assumed only one column contains the minimum value hence Data(0).
Dim test as double
test = datatable.Compute("min(sourcecolumn)", "")
Dim Data() As DataRow = datatable.Select("sourcecolumn = " & test.ToString())
Dim column2 = Data(0)(1)
All you have is a value but you currently have no idea what row(s) contain that value. You can use the table's Select method to get the row(s) that contain that value in that column. Once you have the row(s), you can do whatever you want with it/them:
Dim minValue = CDbl(myDataTable.Compute("MIN(MyColumn)", Nothing))
Dim rows = myDataTable.Select($"MyColumn = {minValue}")
For Each row In rows
'Use row here.
Next
Select always returns an array, even if there is only one row, so the loop will always work. If you know that there will never be more than one match, you can just get the first element directly.

Perform Vlookup from sheet 1 to sheet 2 and pass a matching value found in sheet 2 to external API

I have Sheet 1 which contains a bunch of data including names of users. Sheet 2 contains users' names with their ID. I want to do some kind of mapping and I am not sure how to go about it.
Ideally, I would like to read a range of rows, say AA: AE and match the names in sheet 1 with the ones in sheet 2 with ID, and then POST the ID in sheet 2 to an external API.
Here is the code:
function getData(startRow, endRow) {
var ss = SpreadsheetApp.getActive();
//Temporary calc sheet
var calcSheet = ss.getSheetByName("CALC_SHEET");
//Clear data range from previous calculations
calcSheet.getDataRange().clear();
//Clear data range from previous calculations
calcSheet.getDataRange().clear();
//formula template
var formulaTemplate = '{ArrayFormula(IFERROR(VLOOKUP(Form Responses 1!AF1{{startRow}}:AL{{endRow}},{UserID mapping!B1:B132,user_ids!C1:C132},2,false),"")),QUERY(UserID mapping!A{{startRow}}:C{{endRow}}, "select *")}';
//insert startRow and endRow into the formula
var formula = formulaTemplate.split("{{startRow}}").join(startRow).split("{{endRow}}").join(endRow);
//insert formula into the helper sheet
calcSheet.getRange(1, 1).setFormula(formula);
//write changes to the spreadsheet
SpreadsheetApp.flush();
var values = calcSheet.getDataRange().getValues();
//overwrite formula with values
calcSheet.getDataRange().setValues(values);
return values;
}
Here's a quick example to get you up and running. Below is my sheet with mock user data (not sorted):
The 2nd sheet contains user names mapped to their respective ids:
Step 1
The task is basically to match unique user id from the 'user_ids' sheet to each row in 'user_data' table.
=ArrayFormula(IFERROR(VLOOKUP(user_data!A1:A10,{user_ids!B1:B10,user_ids!A1:A10},2,false),""))
Note that because VLOOKUP uses the first column as the index column, I had to reverse the order of columns in the user_ids sheet so that column B (name column) comes first. This function returns user ids for every single row in 'user_data' (in correct order).
The last step is to display other columns from user data. Final formula
={ArrayFormula(IFERROR(VLOOKUP(user_data!A1:A10,{user_ids!B1:B10,user_ids!A1:A10},2,false),"")),QUERY(user_data!A1:C10, "select *")}
Result:
What if you want to do the same thing programmatically? You can use the same formulas! The downside is that you may need to create a temporary sheet for calculations - think of it as your cache or database. Of course, you can hide the helper sheet or delete it when it's not in use.
Below is the function that utilizes the above approach to return specified rows matched with their ids. Of course, you can still make it better by passing column indexes and handling errors
function getData(startRow, endRow) {
var ss = SpreadsheetApp.getActive();
//Temporary calc sheet
var calcSheet = ss.getSheetByName("calc_sheet");
//Clear data range from previous calculations
calcSheet.getDataRange().clear();
//formula template
var formulaTemplate = '{ArrayFormula(IFERROR(VLOOKUP(user_data!A{{startRow}}:A{{endRow}},{user_ids!B1:B10,user_ids!A1:A10},2,false),"")),QUERY(user_data!A{{startRow}}:C{{endRow}}, "select *")}';
//insert startRow and endRow into the formula
var formula = formulaTemplate.split("{{startRow}}").join(startRow).split("{{endRow}}").join(endRow);
//insert formula into the helper sheet
calcSheet.getRange(1, 1).setFormula(formula);
//write changes to the spreadsheet
SpreadsheetApp.flush();
var values = calcSheet.getDataRange().getValues();
//overwrite formula with values
calcSheet.getDataRange().setValues(values);
return values;
}

VBA - How to refer to column names in Array?

To start, I have a scraping function that scrapes a table from a web page, and stores the data in a 2D array.
The 2D array starts from row 0 to however many rows of data are on the page, and columns 1 to however many columns there are.
Row 0 simply contains all the column names.
My ReDim:
ReDim Addresses(0 To lngTotalRecords, 1 To columns.Count) As String
I've then stored this 2D array into a scripting dictionary called dictClients, as there are multiple clients that all have their own entries for the same table on the web page.
So in my dictionary I have something like the following to refer to a particular address table for a particular client:
dictClients(1)("Addresses")
dictClients(2)("Addresses")
I now want to be able to check if a cell in a certain row contains a specific value, however the web page allows the columns to be reorganized so that:
dictClients(1)("Addresses")(1,1) 'row 1 column 1
will not always refer to the "Street Number" column. The street number column could be the following for someone else for example:
dictClients(1)("Addresses")(1,3) ' row 1 column 3
Given that these cells:
dictClients(1)("Addresses")(0,1) '(0,2) (0,3) etc.
all refer to the column's names, what's the best way for me to find the position of a particular named column?
Example: I want to get the value of the Postal Code cell in row 1, so I need to look in
dictClients(1)("Addresses")(1, POSTALCODECOLUMN),
which isn't always in the same position on the web page.
I was thinking of using the following function:
Public Function column(strArr() As String, strColumnName As String)
Dim i As Long
For i = 1 To UBound(strArr, 2)
If strArr(0, i) = strColumnName Then column = i
Next
End Function
But it just feels so lengthy calling it like:
strPostalCode =
dictClients(1)("Addresses")(1,column(dictClients(1)("Addresses"), "Postal Code")
Is there a better and easier way to do this?
Thanks.

Insert tables into word via vb.net

I'm making an addon for word. It must be possible to insert tables. It shall be possible to specify the dimensions and location. When I insert the first table it works fine, but if I insert another table, then the first table get removed and the new one inserted. I am still pretty new to vb.net so, the code may not be the best.
With Globals.WordAddIn.ActiveDocument.Tables.Add(Globals.WordAddIn.ActiveDocument.Range, 1, 1)
.TopPadding = 0
.BottomPadding = 0
.LeftPadding = 0
.RightPadding = 0
.Rows.WrapAroundText = True
.Rows.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage
.Rows.RelativeVerticalPosition = Word.WdRelativeVerticalPosition.wdRelativeVerticalPositionPage
.Rows.HorizontalPosition = dobHorizontal
.Rows.VerticalPosition = dobVertical
.Rows.Height = dobHeight
.Columns.Width = dobWidth
End With
Assuming you're using the code above for adding both tables (possibly in a loop) I think the issue is that you're overwriting the first table with the second one since you use the same range.
The documentation for Tables.Add says:
The range where you want the table to appear. The table replaces the
range, if the range isn't collapsed.
If you change the first line of your code from:
With Globals.WordAddIn.ActiveDocument.Tables.Add(Globals.WordAddIn.ActiveDocument.Range, 1, 1)
to something like
dim range = Globals.WordAddIn.ActiveDocument.Range;
With Globals.WordAddIn.ActiveDocument.Tables.Add(range, 1, 1)
And then after you added your first table you do:
range.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
It should let you add both tables.
However, if you add two tables right after each other I think Word combines them into one table, so you need to add some space in between, for example by using something like:
range.InsertParagraphAfter();
range.Collapse(Word.WdCollapseDirection.wdCollapseEnd); ' need to collapse again to avoid overwriting
I think it might work.