Shifting data to first available cell (not row) in a column - javascript

I have a sheet to monitor inventory. I have a script in the sheet to copy values of rows with data and paste to the first available row on another sheet and then clear the content. This leaves holes in my data on the original sheet. I want to consolidate all my data up to the first available cells in a column so there would never be empty rows between rows with data. Unfortunately, I can't do the same thing I do with the other sheet, as I have certain columns in my row which have formulas that need to remain (Sum formulas to figure weight, etc). If I grab the entire row, it messes up my formulas or the formulas mess up the script (row is not available because it contains a value). So basically what I have is this:
Sheet1 name = 'CURRENT MONTH'
Sheet2 name = 'SHIPPED INVENTORY'
Total Range with data on Sheet1 = A11:W61
Ranges with data that needs to shift up = A11:O61, R11:S61, V11:W61
Ranges with formulas that cannot shift = P11:Q61, T11:U61
I've searched what I could but either I cannot word my dilemma correctly or the answers are beyond my skill level and I can't make them work for me. Any assistance is greatly appreciated!

Without seeing how you are moving your information from one sheet to another, this code copies the entire row to the first row of the inventory sheet:
function myFunction ()
{
...
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var month_sheet = ss.getSheetByName ("CURRENT MONTH");
var shipped_sheet = ss.getSheetByName ("SHIPPED INVENTORY");
var month_values = month_sheet.getDataRange ().getValues ();
for (var i = 11; i < month_values.length && i <= 61; i++)
{
if (test_value) // This is a row you want to move to the other sheet
{
// Create a row in the SHIPPED INVENTORY sheet,
// and copy the full row into it
shipped_sheet.insertRowAfter (1); // Assuming a header row
var shipped_row_1= shipped_sheet.getRange (2, 1); // Get second row in file
var month_range = month_sheet.getRange ((i + 1), 1, 1, month_sheet.getMaxColumns ());
month_range.copyTo (shipped_row_1);
// Now move everything else up
moveRangeUp (month_sheet, "A", i, "O61");
moveRangeUp (month_sheet, "R", i, "S61");
moveRangeUp (month_sheet, "V", i, "W61");
i--; // Since everything moved up, check this row's new information
}
}
...
}
function moveRangeUp (sheet, first_col, row, last_col_row)
{
var from_a1Notation = first_col + (i + 1) + ":" + last_col_row;
var to_a1Notation = first_col + (i);
var copy_range = sheet.getRange (from_a1Notation);
var to_range = sheet.getRange (to_a1Notation);
copy_range.copyTo (to_range);
// After shift up, delete last row of data, since it is also moved up
// Always assuming that the last row is row 61
var delete_a1Notation (first_col + "61:" + last_col_row);
var delete_range = sheet.getRange (delete_a1Notation);
var delete_range.clear ();
}

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.

How to paste values from one sheet to another to last row of specific column

A novice on app scripts, but managed to successfully build my own script through much research, but now my script is running into errors. Below is my current code:
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
ts.getRange(ts.getLastRow()+1, 1,data.length, data[0].length).setValues(data);
ts.getRange(ts.getLastRow()+1, 1,OUdata.length, OUdata[0].length).setValues(OUdata);
}
In the Daily Sales sheet I am copying values from columns B-H and K-Q and pasting them in the last row of the Tracker sheet starting at Column A. The Daily Sales values in Col. K-Q are pasted correctly below the B-H values, so happy with those results.
On the Tracker sheet these values are in Columns A-G. However I have since added formulas in Columns I and J based on the script data and a manual entry in Column H. These formulas are copied down the entire column within the sheet (i.e. row 5000). Since adding these formulas, the script is now pasting values in A5001.
I realize it is because of these formulas, but is there a way to update my script to paste in the last row of column A while maintaining those formulas in Columns I and J? Thanks in advance.
You could create a helper function that computes the first free row of a given column (NOT a column with formulas)
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
var values = data.concat(OUdata).filter(function (r) {return r[0]});
var fr = findFirstFreeRow(ts, 1) // second parameter represents column to check for first free row
ts.getRange(fr, 1,values.length, values[0].length).setValues(values);
}
function findFirstFreeRow(sheet, colNum) {
var v = sheet.getRange(1, colNum, sheet.getLastRow()).getValues(),
l = v.length,
r;
while (l >= 0) {
if (v[l] && v[l][0].toString().length > 0) {
r = (l + 2);
break;
} else {
l--;
}
}
return r;
}
See if that helps?
After deleting the extra empty rows from your 'Tracker' sheet, you can use the appendRow function to create a new row with the manual values (leaving the cells that need formulas blank).
After the manual values are in, you can then get the cells that need formulas and use setFormula on them.
For the sake of brevity, if you wanted column A,B,C,E,F to have manual values and column D to have a formula you would do:
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(['A','B','C','','E','F']);
sheet.getRange(sheet.getLastRow(),4).setFormula('=FORMULA(HERE)');

Add/Subtract Inventory Data in MULTIPLE columns, not just ONE column

What should I do to change the code below (at the very bottom) so that it works for multiple column pairs, not just one column pair?
I am tracking inventory in a sheet, something like this:
ColA ColB ColC ColD ColE ColF ColG ColH
Category Item R+/- G+/- B+/- Red Green Blue
AAA A 1 0 0
AAA B 2 1 0
I want to be able to type numbers into ColC, ColD and ColE and then click a button to subtract those numbers from the totals in ColF, G and H, respectively.
I found a similar question with a great answer here, for ONE column pair:
Google Sheets - How to create add and subtract buttons to track inventory. The code got me started. I'm pretty sure I needed to update the getRange stuff from what it was (shown immediately below) to what is now listed in the whole function code further down. (I also changed some names/variables to better match my inventory needs.)
var updateRange = sheet.getRange(2, 3, maxRows); // row, column, number of rows
var totalRange = sheet.getRange(2, 4, maxRows);
But what do I do with the for section so that it works for all three column pairs, not just for ColC & ColF? I tried adding a "var col in updateValues" but it didn't like col (or column); besides i wasn't sure how to nest it with the var row that's already there. (I did notice that if I changed the 0 after each [row] to 1, it would do the 2nd columns. But it didn't like it when I did "var col in updateValues" and then "updateValue[0][col]".)
function subtractUpdateBulk() {
var sheet = SpreadsheetApp.getActiveSheet();
var maxRows = sheet.getMaxRows();
var updateRange = sheet.getRange(2, 3, maxRows, 3); // row, column, # of rows, # of cols
var totalRange = sheet.getRange(2, 6, maxRows, 3);
var updateValues = updateRange.getValues();
var totalValues = totalRange.getValues();
for (var row in updateValues) {
var updateCellData = updateValues[row][0];
var totalCellData = totalValues[row][0];
if (updateCellData != "" && totalCellData != "") {
totalValues[row][0] = totalCellData - updateCellData;
updateValues[row][0] = "";
}
}
updateRange.setValues(soldValues);
totalRange.setValues(totalValues);
}
If you offer code alone is great. A bit of explanation to go with it would be even better, so I understand the WHY and can hopefully apply it elsewhere.
Your code was pretty close.
This is the substitute code to manage three columns of data movements and three columns of totals. Most of the code is self-explanatory but I'll focus on a couple of points. Its good that you're interested in the why as well as the how, and I've left some DEBUG lines that hopefully with assist.
1) I setup the spreadsheet and sheet using "standard" commands. In this case, I used getSheetByName to ensure that the code always would execute on the desired sheet.
2) I didn't use getMaxRows because this returns "the current number of rows in the sheet, regardless of content.". So if your spreadsheet has 1,000 rows but you've only got, say, 20 rows of data, getmaxRows will return a value of 1,000 and force you to evaluate more rows than are populated with data. Instead I used the code on lines 30 and 30 var Avals and var Alast which use a javascript command to quickly return the number of rows that have data. I chose Column A to use for this, but you could change this to some other column.
3) Rather than declare and get values for two ranges (updateRange and totalRange), I declared only one data range totalRange and got the values for all 6 columns. getValues is a fairly time costly process; by getting values for all rows and all columns, you can then pick and choose which columns you want to add together.
The command is:
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6);
The syntax (as you noted) is row, column, # of rows, # of cols.
The start row is the row following the header => (NumHeaderRows + 1).
The start column is Column C => 3.
The number of rows is the data rows less the header rows => (Alast - NumHeaderRows)
The number of columns is ColC, ColD, ColE, ColF, ColG => 6
4) for (var row in totalValues) {
This was a new one for me, and its so simple, so I keep it.
5) I used two arrays, just as you did. I used one array (RowArray) to build the values for each row, and the second array (MasterArray)is cumulative.
BTW, in your code soldValues isn't ever declared and no values are ever assigned to it.
6) The most important thing is the calculation of the adjustments on each line:
For the sake of clarity, I declared three variables totalRed, totalGreen and totalBlue, and showed how the totals were calculated for each value. This wasn't strictly necessary (I could have just pushed the formulas for each new totals), but they enable you to how each movement is calculated, and the column numbers used in each case.
function so_53505294() {
// set up spreadsheet
// include getsheetbyname to ensure calculations happen on the correct sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
//set some variables
var NumHeaderRows = 1; // this is the number of header rows - user changeable
var totalRed = 0;
var totalGreen = 0;
var totalBlue = 0;
// arrays used later in loop
var RowArray = []; // row by row values
var MasterArray = []; // row by row, cumulative values
// get number of rows of data
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// Logger.log("the last row = "+Alast);// DEBUG
// define the entire data range
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6); // row, column, # of rows, # of cols
//Logger.log("the TotalRange = "+totalRange.getA1Notation());//DEBUG
// get the data fior the entire range
var totalValues = totalRange.getValues();
// loop through thr rows
for (var row in totalValues) {
// clear RowArray at the start of each new row
RowArray = [];
// calculate the new totals
totalRed = totalValues[row][0] + totalValues[row][3];
totalGreen = totalValues[row][1] + totalValues[row][4];
totalBlue = totalValues[row][2] + totalValues[row][5];
//Logger.log("row = "+row+", Movement RED = "+totalValues[row][0]+", Old Stock RED = "+totalValues[row][3]+", New RED = "+totalRed); //DEBUG
//Logger.log("row = "+row+", Movement GREEN = "+totalValues[row][1]+", Old Stock GREEN = "+totalValues[row][4]+", New GREEN = "+totalGreen); //DEBUG
//Logger.log("row = "+row+", Movement BLUE = "+totalValues[row][2]+", Old Stock BLUE = "+totalValues[row][5]+", New BLUE = "+totalBlue); //DEBUG
// update the RowArray for this row's values
RowArray.push(0, 0, 0, totalRed, totalGreen, totalBlue);
// update the MasterArray for this row's values
MasterArray.push(RowArray);
}
// Update the data range with the new Master values.
totalRange.setValues(MasterArray);
}

Synchronizing function calls on google app script

I'm working with a sheet that has around 700 rows, and each row has a phrase on the first Column.
I'm trying to write a script, that by given a sheet similar to the one I have - would add an additional column to the right with the number of words in this row and after adding the counting on each row, it will sort this sheet by descending order based on the added column.
What happens, is that apparently there's asynchronicity, it first sorts by the second column and only then adds the counting... I looked around and couldn't find a decent solution to this problem... any ideas?
The code:
function sortByNumOfKeywords(lang, col, sheet) {
var file = findFileByName("Translate Keywords List");
var spreadsheet = SpreadsheetApp.openById(file.getId());
//Getting the sheet name from the list above
var sheet = findSheetByName(spreadsheet,'Spanish');
//Getting Table bounds (Two-Dimensional array);
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
//add one column (Column H) to hold the number of words of each keyword in the first column
addWordsCounterCol(sheet, col, lastRow, lastColumn);
//sorting by the added column(H)
sheet.sort(8, false);
}
//sets a new column at the end of the table with the number of keywords of each cell in the given Row
function addWordsCounterCol(sheet, col, lastRow, lastCol){
sheet.activate();
var colChar = String.fromCharCode(65 + col-1); //Converts the Col number to it cohherent Big letter
var formulas = []; //Formulas will be set as a two-dimensional array
for(var i=2; i<lastRow+1; i++){
var cell = sheet.getRange("" +colChar + i);
var numOfKeys = "=COUNTA(SPLIT(trim("+ colChar + i + "), \" \"))";
formulas[i-2] = []; //initializing row with a new array
formulas[i-2].push(""+numOfKeys); // each row has only one column, with this specific String
}
var cells = sheet.getRange("H2:H"+(lastRow));
cells.setFormulas(formulas);
}
Try use flush(). Applies all pending Spreadsheet changes.
It works fine for me in your code:
...
SpreadsheetApp.flush();
//sorting by the added column(H)
sheet.sort(8, false);
...

Google app script - looping through the rows in a spreadsheet

I am trying to loop through rows within a spreadsheet and identify if a particular row has the key word "hello" and move that entire row into a new spreadsheet.
I have attempted the following code. The code works for the first row but doesn't loop through and stops after the first row. Expanding the range selection to "C1:E32" does not help.
function Edit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activatedSheetName = ss.getActiveSheet().getName();
var ActiveSheet = ss.getSheetByName("ActiveSheet"); // source sheet
var MoveDatatoThisSheet = ss.getSheetByName("MoveDatatoThisSheet"); // target sheet
var re = new RegExp(/(Hello)/i);
var startRow = 1;
var endRow = ss.getLastRow();
var getRange = ss.getDataRange();
var getRow = getRange.getRow();
for (var ree = startRow; ree <= endRow; ree++) {
// if the value in column D is "Approved", move the row to target sheet
cellValue = ss.getRange("C1:E1");
if (cellValue.getValue().match(re)) {
// insert a new row at the second row of the target sheet
MoveDatatoThisSheet.insertRows(2, 1);
// move the entire source row to the second row of target sheet
var rangeToMove = ActiveSheet.getRange(/*startRow*/ getRow, /*startColumn*/ 1, /*numRows*/ 1, /*numColumns*/ ActiveSheet.getMaxColumns());
rangeToMove.moveTo(MoveDatatoThisSheet.getRange("A2"));
// add date and time of when approved to target row in column E
MoveDatatoThisSheet.getRange("E2").setValue(Date());
// delete row from source sheet
ActiveSheet.deleteRow(cellValue, 1);
}
}
}
Your loop never uses the variable ree, it only operates with cellValue = ss.getRange("C1:E1").
Another problem is that deletion shifts the rows under the deleted one, possibly causing subsequent operations to act on a wrong row. When you go through an array of rows, deleting some of them, do it bottom up, not top down.
for (var ree = endRow; ree >= startRow; ree--) {
var rangeToCheck = ss.getRange(ree, 3, 1, 3); // 3 columns starting with column 3, so C-E range
if (rangeToCheck.getValues()[0].join().match(re)) { // joining values before checking the expression
MoveDatatoThisSheet.insertRows(2,1);
var rangeToMove = ActiveSheet.getRange(/*startRow*/ getRow, /*startColumn*/ 1, /*numRows*/ 1, /*numColumns*/ ActiveSheet.getMaxColumns());
rangeToMove.moveTo(MoveDatatoThisSheet.getRange("A2"));
// add date and time of when approved to target row in column E
MoveDatatoThisSheet.getRange("E2").setValue(Date());
// delete row from source sheet
ActiveSheet.deleteRow(ree);
}
}
If the goal is to check only column D (say), the code simplifies slightly
var rangeToCheck = ss.getRange(ree, 4); // column D in row ree
if (rangeToCheck.getValue().match(re)) { // joining values before checking the expression
Performance
As Google recommends, one should avoid multiple calls to getValues / setValues and such, instead grabbing all necessary data at once, processing it, and making batch changes at once. E.g., instead of placing it a row in another sheet, add it to an array; when the loop ends, place the entire array in that sheet.

Categories

Resources