Array.splice(index, 0, data) returns mysterious null and increase array size (length) - arrays

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?

Related

Apps Script: Move SELECTED rows from one sheet to the last available rows of another sheet

The code below is from here and was originally written by user #Cooper. All it does is that it copies the values of the columns specified in the array colA = [1,3,2,4] from SHEET 1 and add them to SHEET 2. I want to re-write the 2 last lines of the code, so that the values coming from SHEET 1 are added to the last available rows in SHEET 2. By "2 last lines", I mean these last lines of the code:
drng = des.getRange(1,1,drngA.length,drngA[0].length);//destination array controls size of destination range.drng.setValues(drngA);
Any idea how to get this done?
Thank you so much in advance for your help!
function Copy_data()
{
var src = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET 1");
var des = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET 2");
var srng = src.getDataRange();
var srngA = srng.getValues();
var drngA = [];
var colA = [1,3,2,4];//indirect data column selection
for (var i = 0; i < srngA.length;i++ )
{
var k = 0;
drngA[i]=[];
for(var j=0;j<srngA[i].length;j++)
{
drngA[i][k++] = srngA[i][colA[j]-1];
}
drng = des.getRange(1,1,drngA.length,drngA[0].length);//destination array controls size of destination range.
drng.setValues(drngA);
}
}
You can get the last row of the destination Sheet and after, add it to your getRange() function. The method proposed by #soMario will not work because it makes requests inside the loop while you update the Sheet, and this causes it to get different values every time you call des.getLastRow().
The simplest solution is to take the getLastRow() request outside of the loop and then include it in getRange. Note that one is added to it so that it does not overwrite the last row with the setValue() request.
Code.gs
function Copy_data() {
...
var lastRow = des.getLastRow() + 1
for (var i = 0; i < srngA.length;i++ ){
...
drng = des.getRange(lastRow,1,drngA.length,drngA[0].length)
...
}
Documentation
getRange(row, column, numRows, numColumns)
getLastRow()

set value on sidebar

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.

Google Apps Script: how to create an array of values for a given value by reading from a two column list?

I have a set of data in a Google spreadsheet in two columns. One column is a list of article titles and the other is the ID of a hotel that is in that article. Call it list1.
Example data
I would like returned a new list with article titles in one column, and an array of the hotel IDs in that article in the other column. Call it list2.
Example data
There are thousands of lines that this needs to be done for, and so my hope was to use Google Apps Script to help perform this task. My original thinking was to
Create column 1 of list2 which has the unique article titles (no script here, just the G-sheets =unique() formula.
Iterate through the titles in list2, looking for a match in first column of the list1
If there is a match:
retrieve its corresponding value in column 2
push it to an empty array in column two of list2
move onto next row in list1
if no longer a match, loop back to step 2.
I've written the following code. I am currently getting a type error (TypeError: Cannot read property '0' of undefined (line 13, file "Code")), however, I wanted to ask whether this is even a valid approach to the problem?
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var lastRow = outputSheet.getLastRow();
var data = outputSheet.getRange(2,1,lastRow,2).getValues();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var itemIDS = [];
for (var i=1; i<=data.length; i++) {
var currentArticle = data[i][0];
var lookupArticle = workingSheet[i][0];
if (currentArticle === lookupArticle) {
var tempValue = [workingSheet[i][1]];
itemIDS.push(tempValue);
}
}
}
Use a simple google sheets formula:
You can use a very simple formula to achieve your goal instead of using long and complicated scripts.
Use =unique(list1!A2:A) in cell A2 of list2 sheet to get the unique hotels.
and then use this formula to all the unique hotels by dragging it down in column B.
=JOIN(",",filter(list1!B:B,list1!A:A=A2))
You got the idea right, but the logic needed some tweaking. The "undefined" error is caused by the workingSheet[i][0]. WorkingSheet is a Sheet object, not an array of data. Also, is not necessary to get the data from list2 (output), it is rather the opposite. You have to get the data from the list1 (source) sheet instead, and iterate over it.
I added a new variable, oldHotel, which will be used to compare each line with the current hotel. If it's different, it means we have reached a different Hotel and the data should be written in list2.
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var outLastRow = outputSheet.getLastRow();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var sourceValues = workingSheet.getRange("A2:B" + lastActiveRow).getValues();
var itemIDS = [];
var oldHotel = sourceValues[0][0]; //first hotel of the list
for (var i = 0; i < sourceValues.length; i++) {
if (sourceValues[i][0] == oldHotel) {
itemIDS.push(sourceValues[i][1]);
/*When we reach the end of the list, the oldHotel variable will never be different. So the next if condition is needed. Otherwise it wouldn't write down the last Hotel.
*/
if (i == sourceValues.length - 1) {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i][0], itemIDS.toString()]
]);
}
} else {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i - 1][0], itemIDS.toString()]
]);
oldHotel = sourceValues[i][0]; //new Hotel will be compared
outLastRow = outputSheet.getLastRow(); //lastrow has updated
itemIDS = []; //clears the array to include the next codes
}
}
}
I also converted the itemIDS array to a String each time, so it's written down in a single cell without issues.
Make sure each column of the Sheet is set to "Plain text" from Format > Number > Plain Text
References
getRange
setValues
toString()

getDisplayValues is not returning a 2-D array

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

Improve function delete rows by contain value

I have working code, that delete row from sheet if column corresponds to one of the conditions. It based on arrays and because of it works much more faster than standard google sheet deleteRow function. I call it like this:
deleteRowsBV('SIZ',4,'0','')
where
deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch)
What I want is a call function with more or less and equal signs, like this:
deleteRowsBV('SIZ',4,<='0',=='')
But in case of my main function, it doesn't work, when I specify a variable instead of a sign and a value.
Here is main function:
function deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(listName);
var DataLengBefore = sheet.getLastRow();
var DataColLeng = sheet.getLastColumn();
var data = sheet.getRange(1,1,DataLengBefore,DataColLeng).getValues();
for (var i = data.length-1; i >= 0; i--) {
if (data[i][ColNum] <= FirstSearch||data[i][ColNum] == SecondSearch) {
data.splice(i, 1);
}
}
sheet.getRange(10, 1, DataLengBefore,DataColLeng).clearContent();
sheet.getRange(10, 1,data.length,5).setValues(data);
}
Based from this related post, spreadsheet rows and columns are numbered starting at 1, for all methods in the SpreadsheetApp, while javascript arrays start numbering from 0. You need to adjust between those numeric bases when working with both. When deleting rows, the size of the spreadsheet dataRange will get smaller; you did take that into account, but because you're looping up to the maximum size, the code is complicated by this requirement. You can simplify things by looping down from the maximum.
You may refer with this thread. However, this only looks at the value from a single cell edit now and not the values in the whole sheet.
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//{"source":{},"range":{"rowStart":1,"rowEnd":1,"columnEnd":1,"columnStart":1},"value":"1","user":{"email":"","nickname":""},"authMode":{}}
try {
var ss = e.source; // Just pull the spreadsheet object from the one already being passed to onEdit
var s = ss.getActiveSheet();
// Conditions are by sheet and a single cell in a certain column
if (s.getName() == 'Sheet1' && // change to your own
e.range.columnStart == 3 && e.range.columnEnd == 3 && // only look at edits happening in col C which is 3
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if ( !e.value || e.value == 0) { // Delete if value is zero or empty
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}

Resources