Excel VBA>Javascript: Loop through rows inserting rows where cell values differ - javascript

I'm struggling to get javascript to run in Googlesheets to do the following:
Loop through column A:A comparing the above cell value to the below cell value
Where, for example say A2 isn't the same as A3 then insert two rows beneath it
Copy the last value above the first blank row and offset down one row into the first new inserted blank row and paste it.
Now I managed to find this example of something similar to what I'm trying to do at the basic level (hence my question here):
Insert row between different data and it's from that that I got this:
function doSomething() {
var spreadsheet = SpreadsheetApp.openById('mySheetID'),
tab = spreadsheet.getSheets()[0], // Get the first tab
values = tab.getRange(2, 1, tab.getLastRow(), 3).getDisplayValues(), //Get the values beginning in the second row because of the headers
newValues = [], // Empty array where we're gonna push the new values
lastName;
// Loop through all the values
for(var i = 0; i <values.length; i++){
// If the last name is different from the current name or the last date is different from the current date add an empty "row" to the new array
if((lastName != undefined) && (values[i][1] != lastName)){
newValues.push(['','','']);
}
//Add the current row to the new array
newValues.push(values[i]);
//Sets the lastDate and lastName to the current values for the next evaluation
lastName = values[i][1];
}
//Sets the new values
tab.getRange(2,1,newValues.length,3).setValues(newValues)
},
This does insert a new row but at every other row so I suspect that it's picking up on data that's in Column C (which is different on every row) as opposed to the data in Column A.
I've Googled a lot to find out even how to open a sheet with the SpreadsheetApp.openById bit and so I'm asking for help or if you can point out bits of the above code to look into. I know it's been very helpfully commented but as I said I'm completely new to javascript.
If it helps there is a an Excel/VBA script that works perfectly:
Sub InsertRowsAtValueChange()
Dim i As Long
Application.ScreenUpdating = False
For i = Range("A" & Rows.Count).End(xlUp).Row To 2 Step -1
If Range("A" & i - 1).Value <> Range("A" & i).Value Then
Rows(i).Resize(2).Insert
Range("M" & i).Value = Range("A" & i - 1).Value
End If
Next
Application.ScreenUpdating = True
End Sub
They're some diagrams in the below VBA question but can't see how to do that here, sorry: https://www.mrexcel.com/forum/excel-questions/1034334-insert-blank-row-offset-copy-paste.html#post4964822
Thanks for your time and any help, I have no experience with javascript, sorry.
Max.
QUICK UPDATE 09/12/17:
I've managed to get parts one and two off of my "Wish List" completed now. I don't fully understand it but I've commented where I can to remind myself later:
function importFromSDE() {
// Open the Google Sheet
var spreadsheet = SpreadsheetApp.openById('mySheetID'),
// Get the tab by name
tab = spreadsheet.getSheetByName('Testing'),
// Get the values beginning in the second row because of the headers
// Values from Row 2, Column 1, Down to last row, across 11 columns
values = tab.getRange(2,1,tab.getLastRow(),11).getDisplayValues(),
// Empty array where we're gonna push the new values
newValues = [],
lastT1Item;
// Loop through all the values
for(var i=0; i<values.length; i++){
// If the last name is different from the current name or the last date is different from the current date add an empty "row" to the new array
if((lastT1Item != undefined) && (values[i][0] != lastT1Item)){
newValues.push(['','','','','','','','','','','']);
newValues.push(['','','','','','','','','','','']);
}
//Add the current row to the new array
newValues.push(values[i]);
//Sets the lastT1Item to the current values for the next evaluation
lastT1Item = values[i][0];
}
//Sets the new values
tab.getRange(2,1,newValues.length,11).setValues(newValues)
}

Related

How to get the next empty row in a specific column google spreadsheet using google apps script

I want to update the next rows in a spreadsheet with some data and for that, I need to find the last row. However, I need to get the last value in column A and not other columns. The function below works well and gets me the last row in column A that has value but returns that one and I need the row below the one with the value.
function getLastDataRow(sheet) {
var lastRow = sheet.getLastRow();
var range = sheet.getRange("A" + lastRow);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
}
I am new to both JavaScript (python user) as well as Google Apps Script so I could really use some help with this as I have been trying all evening to get the first row in column A that is empty after the one with the value. For example if A20 has the last value, I want to return 21 and not 20.
Basically, I was using the following code to get a range and update the cells with new content:
if (projdoc.match(regExp)) {
projdocnum = regExp.exec(projdoc)[1];
var target = "A" + (getLastDataRow ( sheet )+1);
var regExp2 = new RegExp("[A-Za-z]+([0-9]+)","g");
var targetnum = regExp2.exec(target)[1]; var rangesheet = sheet.getRange(target+":D"+targetnum);
rangesheet.setValues([[ID, projectname, projdoc, projdocnum]]);
}
The only thing I changed was in earlier versions I had :
var target = "A" + (getLastDataRow ( sheet )); and I just added +1 to it to get the next row.

Dynamically edit columns in google sheets

Context
I am creating a database of stock price data. I am currently using the below onEdit function:
function onEdit(e) {
// Get the spreadsheet that this will apply to, the tabs and columns
var ss = e.source.getActiveSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16]; // the columns to isolate for special reasons
var excludeCells = ["M1","I1","I2"];
// What is the criteria for action to be taken? If the terms defined in excludeTabs are met, then move onto the next criteria (indexOf used because we stored it as an array
if(excludeTabs.indexOf(ss.getName())===-1){
// The range from the spreadsheet.
// Scripts expects an event in a cell
var cell = e.range;
// For the entire column of this cell
var col = cell.getColumn();
// Within the universe of tabs that this script applies to, we want to exclude the columns in the array
if(excludeColumns.indexOf(col)===-1)
// Within the universe of tabs and columns, we want to exclude certain cells
if(excludeCells.indexOf(cell)===-1)
// Need to make sure it only applies to formulas
if(cell.getFormula() !== ""){
var destination = ss.getRange(4, col, ss.getLastRow()-1, 1);
cell.copyTo(destination);
}//End of the remaining universe of data taking out the exceptions
}//End Tab criteria
}//End function
This allows for an edit in some of the columns to be performed when I edit the cell. So far it works but with a few kinks.
Problem 1
Sometimes, when I edit a cell above the fourth row of a column, it edits the entire column despite me telling it to start from the fourth row. This happened just a few minutes ago in a cell I told it to exclude above "I2". Is there anything wrong with the code I have written to this effect?
Problem 2
I tried creating other exceptions for the code, where for some specified ranges, it will only edit from a different cell range. Not the fourth cell of every column but of say the 10th cell. I tried adding it below var destination = ss.getRange(4, col, ss.getLastRow()-1, 1) but it did not work. I also tried creating a separate onEdit function for a different cell location but it also did not work.
So far I have been using the sheet formulas like the below:
IFERROR(IFS(C4="Returns","",AND(C4="",C5="",C6="",C7="",C8=""),"",AND(ISNUMBER(C4),ISNUMBER(C5),ISNUMBER(C6),ISNUMBER(C7),ISNUMBER(C8)),COVAR($C4:$C8,'FTSE 250'!J5:J9)),""))
But this just gets messy. Sometimes there is data in the cell and so it would render formulas like the above useless. An example is the below picture.
Update I want the onEdit to start and drag down from the 10th row of the column but only for that column (this is the row in that column that I will be editing). I also want to be able to do this for other columns (start the automatic copy down process from different rows).
This is the range I am trying to edit
[
Update 2
...
if(!excludeTabs.includes(ss.getName()) &&
!excludeColumns.includes(col) &&
!excludeCells.includes(cell.getA1Notation()) &&
cell.getFormula() !== ""
){
if(col==33){
var destination = ss.getRange(8, col, ss.getMaxRows()-7, 1);
cell.copyTo(destination);
}
else if(col===30){
var destination = ss.getRange(8, col, ss.getMaxRows()-7, 1);
cell.copyTo(destination);
}
else{
var destination = ss.getRange(4, col, ss.getMaxRows()-3, 1);
cell.copyTo(destination);
}
}
Explanation:
Issues:
Your if statements don't have closed brackets with code in it.
Here if(excludeCells.indexOf(cell)===-1) there is a problem:
var excludeCells = ["M1","I1","I2"]; is an array of strings and var cell = e.range; is a range object. You are actually comparing two different things (a string vs a range object.)
Instead you want to replace: if(excludeCells.indexOf(cell)===-1) with if(excludeCells.indexOf(cell.getA1Notation())===-1).
Improvements:
Instead of using multiple if statements which at the end lead to one single code block, use one if statement with multiple conditions.
Also this range getRange(4, col, ss.getLastRow()-1, 1); does not make a lot of sense either. It makes more sense to use ss.getLastRow()-3 because you are starting from 3.
Instead of using excludeCells.indexOf(cell.getA1Notation())===-1 which is a long expression, you can use includes() like that !excludeCells.includes(cell.getA1Notation()).
Solution:
function onEdit(e) {
var ss = e.source.getActiveSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16]; // the columns to isolate for special reasons
var excludeCells = ["M1","I1","I2"];
var cell = e.range;
var col = cell.getColumn();
if(!excludeTabs.includes(ss.getName()) &&
!excludeColumns.includes(col) &&
!excludeCells.includes(cell.getA1Notation()) &&
cell.getFormula() !== ""
){
if(col==33){
var destination = ss.getRange(10, col, ss.getMaxRows()-9, 1);
cell.copyTo(destination);
}
else{
var destination = ss.getRange(4, col, ss.getMaxRows()-3, 1);
cell.copyTo(destination);
}
}
}
Please Note:
getLastRow() returns the last row with content. For example if you have 10 columns and the first 10 have last row with content to be 55 but there is a random value in column 20 at the end of the sheet let's say row 900 then 900 will be the last row in your sheet. Be careful with that, otherwise you will need other approach to get the last row with content. Formulas are content too. So a formula all the way to the bottom of the sheet might determine what getLastRow returns.
Try this:
function onEdit(e) {
var sh = e.range.getSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16];
var excludeCells = ["M1","I1","I2"];
if(excludeTabs.indexOf(sh.getName())==-1 && excludeColumns.indexOf(e.range.columnStart)==-1 && excludeCells.indexOf(e.range.getA1Notation())==-1 && e.range.getFormula()!=""){
e.range.copyTo(sh.getRange(4, e.range.columnStart, sh.getLastRow()-3, 1));//The numbers of rows to the bottom is sh.getLastRow()-3 -1 will have to roll off of the bottom of the spreadsheet which will give you out of range errors
}
}
}

Sync row data based on unique ID in google sheets scripts

I currently have a code that does something very similar to this, but im not sure the small change i need to make to have it work correctly. Right now, the code below compares two rows of unique IDs and if the IDs are the same, it copies the cell in the "Comments" column to the other sheet.
function setComments() {
var ss = SpreadsheetApp.getActive(),
compare1 = "", compare2 = "",
outputSheet = ss.getSheetByName("Sheet2"),
sourceSheet = ss.getSheetByName("Sheet1"),
range1 = outputSheet.getDataRange(),
range2 = sourceSheet.getDataRange(),
lastCol1 = range1.getNumColumns(),
lastCol2 = range2.getNumColumns(),
values1 = range1.getValues(),
values2 = range2.getValues(),
// get the range of the titles
titleSection1 = outputSheet.getRange(1,1,1, lastCol1),
titleSection2 = sourceSheet.getRange(1,1,1, lastCol2),
// get the values from the titles
titles1 = titleSection1.getValues(),
titles2 = titleSection2.getValues(),
// get the column # for "ID" and "comment"
idCol1 = titles1[0].indexOf("ID"),
idCol2 = titles2[0].indexOf("ID"),
commentsCol1 = titles1[0].indexOf("comment"),
commentsCol2 = titles2[0].indexOf("comment");
// get the IDs from range1
for (i = 1; i < values1.length; i++) {
compare1 = values1[i][idCol1];
// get the IDs from range2
for (j = 1; j< values2.length; j++){
compare2 = values2[j][idCol2];
// if same ID, change the values array
if (compare1 == compare2) {
values1[i][commentsCol1] = values2[j][commentsCol2];
}
}
}
// set values based on the values array
range1.setValues(values1);
}
Instead, if there is a change made to any cell on sheet 1, it will find the identical cell based on unique ID in the other sheet and sync the change. What change do i need to make to have this work?
For example, if I change what the office is in the row of ID 1 of sheet 1, it will make the identical change for ID 1 in sheet 2.
Here is an example sheet of what im working with:
Sheet 1:
ID Comment Number Office Clinician
1 good 22345 Dallas
2 bad 12345 Denton
3 good 95954 Lubbock
4 bad 20204 FT.W
5 bad 11111 Denton
6 good 02944 Preston
Sheet 2:
ID Comment Number Office Clinician
1 good 22345 Dallas
3 good 95954 Lubbock
5 bad 11111 Denton
You have one data set on Sheet 1, and a subset of that data set on Sheet1. The key field is the "ID". If there is a change to data on Sheet 1, and the ID relating to that change is found on Sheet 2, then you want to update the relevant dataset on Sheet 2.
The key aspects of this answer are:
onEdit(e): This is an simple Trigger.
e.range: "range" is an Event Object. By using the attribute 'e'; it is possible to recover a substantial amount of information about the changes. In addition, the event objects can be further used to obtain more information - such as (in this case) the Row, Column and Sheet Name.
filter(String).length: Sometimes getting the last row of data is problematic. The answer gets all the data in Column A, and uses the Javascript "array.filter" method. In this case is simply counts the values as strings, andf the resulting value is equal to the Last Row of data.
the "IF" statement evaluates for several attributes:
Is the edit on Sheet1?
Is the edit on a row between the header and last row?
Is the edit in a column that contains data?
the operator is "&&" which requires that each of the attributes must return true.
targetdata.map(function(e){return e[0];});: the script gets ALL the data on Sheet2, but uses the Javascript array.map method to generate a subject of just the values in Column 1 (the ID column).
targetCol1.indexOf(sourceID);: The script uses the Javascript "array.indexOf"; if the ID is found, it will return the index number of the row on Sheet2; if the value isn't found, it will return "-1". The enables a logic statement to be written that will be executed only if the value is not "-1"
target.getRange(+result+1,1,1,sourceLC).setValues(sourcedatatocopy): the last lines of the script get the values in the edited row on Sheet 1, and then update the values on Sheet2. Note: it updates all the values for the matched ID - rather than identifying the changed field and updating only that field.
function onEdit(e) {
// setup spreadsheet and sheet names
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSN = "Sheet1";
var source = ss.getSheetByName(sourceSN);
var targetSN = "Sheet2";
var target = ss.getSheetByName(targetSN);
// get row, column and sheet of the edited sheet
var row = e.range.getRow();
var col = e.range.getColumn();
var editedsheet = e.range.getSheet().getSheetName();
// Logger.log("DEBUG: editedsheet = "+editedsheet)
// get the ID column from Sheet 1
var SourceCol1Vals = ss.getRange("A2:A").getValues();
var SourceLR = SourceCol1Vals.filter(String).length;
// assign value to last column
var sourceLC = 5;
// test for sheet, row range and column range
if (editedsheet == sourceSN && row >=2 && row <=SourceLR && col <=5){
//Logger.log("DEBUG: this is sheet 1 & the right row and the right Col")
// get the data for this row
var sourcedata = source.getRange(row,1,1,5).getValues();
// get the data from target
var targetdata = target.getDataRange().getValues();
// get only the ID column
var targetCol1 = targetdata.map(function(e){return e[0];});
// get the sourse ID
var sourceID = sourcedata[0][0];
// Logger.log("DEBUG: source ID = "+sourceID)
// search for source ID on the Target list
var result = targetCol1.indexOf(sourceID);
// Logger.log("DEBUG: id was found at "+result);
if (result !=-1){
// if -1 then couldn't find the ID, otherwise it returns the index number where it finds the match
// get the data for the Source ID
var sourcedatatocopy = source.getRange(row,1,1,sourceLC).getValues();
// update the darget for the sourceID data.
target.getRange(+result+1,1,1,sourceLC).setValues(sourcedatatocopy)
}
} else{
// Logger.log("DEBUG: not sheet 1 or right row or right col");
}
return;
}

Is it possible to pull different data sets from one column?

I've been trying to write some code that looks down one column with strings based on some simple formulas. I can't seem to get it to recognize the different sets of data and paste them where I want them.
I have tried re writing the code a few different ways in which is looks at all the data and just offsets the destination row by 1. But it does not recognize that it is pull different data.
Below is the code that works. What it does is starts from the 1st column 2nd row (where my data starts). The data is a list like;
A
1 Customer1
2 item1
3 item2
4 Item3
5
6 Customer2
7 Item1
The formulas that I have in those cells just concatenates some other cells.
Using a loop it looks through column A and find the blank space. It then "breaks" whatever number it stops on, the numerical A1 notation of the cell, it then finds the values for those cells and transposes them In another sheet in the correct row.
The issue I am having with the code this code that has worked the best is it doesn't read any of the cells as blank
(because of the formulas?) and it transposes all to the same row.
function transpose(){
var data = SpreadsheetApp.getActiveSpreadsheet();
var input =data.getSheetByName("EMAIL INPUT");
var output = data.getSheetByName("EMAIL OUTPUT");
var lr =input.getLastRow();
for (var i=2;i<20;i++){
var cell = input.getRange(i, 1).getValue();
if (cell == ""){
break
}
}
var set = input.getRange(2, 1, i-1).getValues();
output.getRange(2,1,set[0].length,set.length) .
.setValues(Object.keys(set[0]).map ( function (columnNumber) {
return set.map( function (row) {
return row[columnNumber];
});
}));
Logger.log(i);
Logger.log(set);
}
What I need the code to do is look through all the data and separate the sets of data by a condition.
Then Transpose that information on another sheet. Each set (or array) of data will go into a different row. With each component filling across the column (["customer1", "Item1","Item2"].
EDIT:
Is it Possible to pull different data sets from a single column and turn them into arrays? I believe being able to do that will work if I use "appendrow" to tranpose my different arrays to where I need them.
Test for the length of cell. Even if it is a formula, it will evaluate the result based on the value.
if (cell.length !=0){
// the cell is NOT empty, so do this
}
else
{
// the cell IS empty, so do this instead
}
EXTRA
This code takes your objective and completes the transposition of data.
The code is not as efficient as it might/should because it includes getRange and setValues inside the loop.
Ideally the entire Output Range could/should be set in one command, but the (unanswered) challenge to this is knowing in advance the maximum number rows per contiguous range so that blank values can be set for rows that have less than the maximum number of rows.
This would be a worthwhile change to make.
function so5671809203() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var inputsheetname = "EMAIL_INPUT";
var inputsheet = ss.getSheetByName(inputsheetname);
var outputsheetname = "EMAIL_OUTPUT";
var outputsheet = ss.getSheetByName(outputsheetname);
var inputLR =inputsheet.getLastRow();
Logger.log("DEBUG: the last row = "+inputLR);
var inputrange = inputsheet.getRange(1, 1,inputLR+1);
Logger.log("the input range = "+inputrange.getA1Notation());
var values = inputrange.getValues();
var outputval=[];
var outputrow=[];
var counter = 0; // to count number of columns in array
for (i=0;i<inputLR+1;i++){
Logger.log("DEBUG: Row:"+i+", Value = "+values [i][0]+", Length = "+values [i][0].length);
if (values [i][0].length !=0){
// add this to the output sheet
outputrow.push(values [i][0]);
counter = counter+1;
Logger.log("DEBUG: value = "+values [i][0]+" to be added to array. New Array Value = "+outputrow+", counter = "+counter);
}
else
{
// do nothing with the cell, but add the existing values to the output sheet
Logger.log("DEBUG: Found a space - time to update output");
// push the values onto an clean array
outputval.push(outputrow);
// reset the row array
outputrow = [];
// get the last row of the output sheet
var outputLR =outputsheet.getLastRow();
Logger.log("DEBUG: output last row = "+outputLR);
// defie the output range
var outputrange = outputsheet.getRange((+outputLR+1),1,1,counter);
Logger.log("DEBUG: the output range = "+outputrange.getA1Notation());
// update the values with array
outputrange.setValues(outputval);
// reset the row counter
counter = 0;
//reset the output value array
outputval=[];
}
}
}
Email Input and Output Sheets

Set value of IDCell to ((value of previous row IDCell) +1)

I have a googlescript which creates a form that allows for existing entries to be edited, or for new entries to be added to a google spreadsheet. If it's a new entry, it takes the field values from the google form and places them into the last row of the google spreadsheet.
var lastCell = sheet.getLastRow(); //entry holds value of current last row position
var entry = sheet.getLastRow()+1; //entry holds value of new last row position
var IDCell = sheet.getRange("C"+entry); //IdCell is in column C, last row of table
The above code is fine..
The part I am hoping to get some help with is the part where I set IDCell to equal ((IDCell of the previous row) +1)
Something like:
IDCell.setValue((sheetData [lastCell][2]) +1);
but this gives me errors "Error encounter: cannot read property "2" from undefined"
Was able to solve on my own:
var lastcell = sheet.getLastRow();
var entry = sheet.getLastRow()+1;
var IDCell = sheet.getRange("C"+entry);
var prev_ID = sheet.getRange("C"+lastcell);
var prev_IDV = prev_ID.getValue();
IDCell.setValue((prev_IDV)+1);
This might have been obvious to some, but I am very new at JavaScripts so it wasn't immediately clear

Categories

Resources