I'm having an issue with a script for Google sheets
I work in education, and basically we have some teachers who would like to have a form to use when they need to refer a student to an administrator.
So, we have a Google form set up and it's answers populate our "Master" tab within the sheet. Then we have a tab set up for each administrator, the idea being that we can write a function that moves data over based on the contents of the "administrator assigned" box (ColumnT in the code below)
The first bit I need to get functional is the code that handles that part, here's what I've been able to come up with (Heavily commented so that you can hopefully see my thought process):
//Get the active spreadsheet and store in a variable
function importStudName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Get"Master" spreadsheet and store in a variable
var master_sheet = ss.getSheetByName("Master");
//Get "Miranda" spreadhseet and store in a variable
var miranda_sheet = ss.getSheetByName("Miranda");
//Column T
var columnT = master_sheet.getSheetValues(0, 20, 0, -1);
//Column D
var columnD = master_sheet.getSheetValues(0, 4, 0, -1);
//Column E
var columnE = master_sheet.getSheetValues(0, 4, 0, -1);
//Loop through Column T and find every value that = "Miranda"
for(var i = 0; i <= columnT; i++) {
if (columnT[i] === "Miranda") {
//Set a variable to hold the last row in the "Miranda" sheet
var miranda_row = (miranda_sheet.getLastRow() + 1);
//Make the row with the "Miranda" string active
//Set a variable that concatenates the Column D and Column E variables (from "master" sheet) into a string
//Copy the new variable to the "Miranda" sheet
.copyValuesToRange(miranda_sheet, 1, 1, miranda_row, miranda_row);
}
}
}
Mostly, I can't seem to figure out the bit that would make select the row that has the "Miranda" keyword in it. Once I had that down, I think it's just a matter of using the .getRange() function to pull the values from columnD and columnE.
I'm a bit rusty on my JavaScript, and this is the first time I've written something for apps script. So it's possilble I'm going about this all wrong :)
Any suggestions would be welcome
The method getSheetValues returns "Object[][] — a two dimension array of values". So, its entries should be accessed as values[i][j] where i is row offset from the upper left corner of the range, and j is the column offset. In particular, your Miranda comparison should be
if (columnT[i][0] === "Miranda")
Another remark: the row and column numbers in Sheets begin with 1, unlike JavaScript array indices. Passing (0, 20, 0, -1); in getSheetValues indicates you may be miscounting things.
Finally, I'd recommend using .getRange(...).getValues() instead of getSheetValues(). For one thing, you can pass A1 notation to getRange, like
var valuesT = sheet.getRange("T1:T").getValues();
to get all values in column T. If it's preferable to not fetch a bunch of empty cells at the bottom, one can use
var lastRow = sheet.getLastRow();
var valuesT = sheet.getRange("T1:T" + lastRow).getValues();
Related
I'm kind of new to working with Google Sheets, and I've stumbled across an issue while creating buttons for a ticket system. I've been having trouble getting the copied data to just fill the other sheet from the second row and down without gaps or overwriting existing data.
The script basically checks if a checkbox of a certain row is ticked, copies the data in that row, moves that data to another sheet, and then resets that row to it's original state. Meaning it doesn't completely delete the row or format, but only copies the contents.
Here's the code I've been working with so far:
function MigrateInProgress() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Copy of New Tickets');
var s2 = ss.getSheetByName('In Progress');
var r = s.getRange('G:G');
var v = r.getValues();
var dataset = [];
for(var i=v.length-1;i>=0;i--)
if(v[0,i]=='true') {
var data = s.getRange(i+1,1,1,6).getValues();
s2.getRange(i+1,s2.getLastColumn()-6,data.length,6).setValues(data);
var datasetrange = s.getRange(i+1,1,1,6);
datasetrange.clear({contentsOnly: true});
datasetrange.clear({formatOnly: false});
s.getRange(i+1,7).setValue(false);
}
}
Here's some screenshots to help contextualize what I mean:
Screenshot 1
Screenshot 2
Screenshot 3
Any help would be greatly appreciated. Thank you!
I believe your goal is as follows.
You want to copy the rows of columns "A" to "F" of the source sheet to the destination sheet when the checkbox of column "G" is checked.
At that time, you want to clear the columns "A" to "F" and uncheck the column "G". And, you want to copy the values to the destination sheet without including the empty rows.
In this case, how about the following modification?
Modified script:
function MigrateInProgress() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Copy of New Tickets');
var s2 = ss.getSheetByName('In Progress');
var v = s.getRange('A2:G' + s.getLastRow()).getValues();
var { values, clear, uncheck } = v.reduce((o, r, i) => {
if (r[6] === true) {
var row = i + 2;
o.values.push(r.splice(0, 6));
o.clear.push(`A${row}:F${row}`);
o.uncheck.push(`G${row}`);
}
return o;
}, { values: [], clear: [], uncheck: [] });
console.log(values)
if (values.length > 0) {
s2.getRange(s2.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
s.getRangeList(clear).clearContent();
s.getRangeList(uncheck).uncheck();
}
}
In this modification, at first, all values are retrieved from the source sheet. And create the values for copying to the destination sheet, and also create the range list for clearing the cells and unchecking the checkboxes.
By the way, the values retrieved by getValues is 2 dimensional array, and in this case the checkbox returns the boolea type. So when you want to use v[0,i]=='true', you can use v[i][0] === true.
About some unclear points, I guessed as follows.
I couldn't understand whether where you want to put the values when the values are copied to the destination sheet. So in this modification, the values are appended to the destination sheet. When you don't want to append the values, please modify s2.getRange(s2.getLastRow() + 1, 1, values.length, values[0].length).setValues(values); to s2.getRange(2, 1, values.length, values[0].length).setValues(values);. By this, the values are always put from the row 2.
References:
reduce()
clearContent() of Class RangeList
uncheck() of Class RangeList
I have a sheet that I have scripted and I am stuck on one spot. The report I get has all of the warehouses together and it's time consuming to manually split them. This is the last part of the script and I am stumped.
Column B is where all of the warehouses are listed. I want all of warehouse 1 to be moved to the page for warehouse 1, warehouse 2 to warehouse 2... etc. This is my code so far and it is not acting as I expect. Calling for orders in warehouse 1 gets me an order from warehouses 2 and 5. Not all of them, just a handful. Any help would be appreciated.
function splitYards(sheet){
//getting the proper range from All Warehouses This works don't change it
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("All Warehouses");
var awLastRow = sheet.getLastRow();
var awRange = sheet.getRange(2,2,awLastRow-1,1);
var awValues = awRange.getValues();
//end of this working
//console.log(awValues);
//begin For statement
for (var i=1; i<awValues.length; i++) {
if (awValues[i] == 1){
var destination = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Warehouse 1");
var drange = sheet.getRange(1,i,1,1);
var dValue = drange.getValues();
//console.log(dValue);
//does not work yet...
// dValue.copyTo(destination);
}
}
}
I have added a simple example to try to explain. Anything that is coming from warehouse 1 gets copied to another sheet that is called Warehouse 1. All items coming from Warehouse 2 gets moved to a sheet called Warehouse 2. I want to move the entire row to the new sheets.
I believe your goal is as follows.
You have a sheet in Google Spreadsheet as shown in your question.
You want to check the column "B" and want to move the rows to the destination sheet.
For example, when the value of column "B" is 1, you want to move the row to the sheet of Warehouse 1 as the appending value.
Modification points:
getValues() returns 2 dimensional array. In this case, awValues[i] == 1 is awValues[i][0] == 1.
In your script, only one sheet is used.
In order to retrieve the values from a row using your for loop, please modify var drange = sheet.getRange(1,i,1,1) to sheet.getRange(i + 2, 1, 1, sheet.getLastColumn()).
For example, when you want to use your for loop, you might be able to modify as follows.
for (var i = awValues.length - 1; i >= 0; i--) {
if (awValues[i][0] == 1) {
var destination = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Warehouse 1");
var drange = sheet.getRange(i + 2, 1, 1, sheet.getLastColumn());
drange.moveTo(destination.getRange(destination.getLastRow() + 1, 1));
}
}
But, when SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Warehouse 1"), moveTo and getValues() is used in a loop, the process cost will be high.
So, in this answer, I would like to propose the following modified script.
Modified script:
function splitYards2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("All Warehouses");
var range = sheet.getRange("A2:D" + sheet.getLastRow());
var values = range.getValues();
var sheets = [...new Set(values.map(r => r[1]))].reduce((o, e) => (o[e] = ss.getSheetByName("Warehouse " + e) || ss.insertSheet("Warehouse " + e), o), {});
[...values.reduce((m, r) => m.set(r[1], m.has(r[1]) ? [m.get(r[1]), r] : r), new Map())]
.forEach(([k, v]) => {
var s = sheets[k];
if (s) s.getRange(s.getLastRow() + 1, 1, v.length, v[0].length).setValues(v);
});
range.clearContent();
}
When this script is run, the rows are moved from All Warehouses sheet to Warehouse # sheet by checking the column "B".
About some unclear points, I guessed as follows.
I couldn't understand that when the sheet of the sheet name retrieved from the column "B" is not found, what you want to do. So, in this modification, when the sheet name of Warehouse # retrieved from the column "B" is not existing, new sheet is inserted as the sheet name.
References:
reduce()
getRange(row, column, numRows, numColumns)
clearContent()
Your code appears to be grabbing the incorrect range to copy to the destination sheet.
The starting index of awValues is 0. Your code calls sheet.getRange(1,i,1,1), but this fails to consider that the indexes in your array do not align with the row numbers on your sheet. Index 0 of your array actually corresponds to row 2 of your sheet, index 1 -> row 3, and so on.
You should adjust the starting row of your range selection as such:
var drange = sheet.getRange(1,i+2,1,1);
Two additional side notes:
Your iteration of awValues begins at index 1, not 0. This means that you're inadvertently skipping over the first row.
awValues is a two-dimensional array because you called getValues. To avoid confusion, it would be best to treat it as such and change your if condition to awValues[i][0] == 1 (i.e. first column of row i).
I'm trying to loop through a column of a Google sheet and apply some formatting.
The data in column 2 looks like this:
name='com.android.dreams.basic'
name='com.android.bluetooth'
name='com.android.browser'
name='com.android.calculator2'
name='com.android.calendar'
name='com.android.camera2'
etc
For whatever reason, the data actually populating the "cell" variable is always "name='(class)'". I'm unsure why this isn't being read as the string that is actually in the cell. If "com.anything" is in the cell it will return '(class)'.
Thoughts? Below is my formatting function.
function ApplyFormatting() {
var currentSheet = SpreadsheetApp.getActiveSheet()
// Get the entire sheet range
var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues()
for(var n = 0; n < values[0][0].length; n++){
var cell = values[n][2]
if (cell == "name='com.android.bluetooth'") {
currentSheet.GetRange(n +1, 1, 1, 1).setBagkgroundColor('green')
}
}
}
Looks like this is only an artifact of the debugger in google-apps-script. The actual values are correct even though the debugger shows incorrect values.
Thanks for your help Jack!
I'm having an issue pulling the correct values out of a for loop in Google Sheets.
Here's my code:
Note: this is a snippet from a larger function
function sendEmails() {
var trackOriginSheet = SpreadsheetApp.getActiveSpreadsheet().getName();
var getMirSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Miranda");
//Set a new object to hold conditional data
var holdingData = new Object();
//Create function to get values from origin sheet
var returnedValues = function (trackOriginSheet) {
//Load dynamic variables into an object via returnedValues()
if (trackOriginSheet === getMirSheet) {
var startMirRow = 2; // First row of data to process
var numRowsMir = 506; // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(startMirRow, 1, numRowsMir, 26);
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k in dataMir) {
var secondRowMir = dataMir[k];
var intRefDescMir = secondRowMir[3];
var intAdminActionsMir = secondRowMir[4];
//Push returned data to holdingData Object
holdingData.selectedData = secondRowMir;
holdingData.refDesc = intRefDescMir;
holdingData.adminActions = intAdminActionsMir;
}
}
}
Here's a copy of the sheet I'm working on
What I need to have happened here first, is track the origin sheet, then create an object to hold data returned from the returnedValues() function. Later, I'll call the properties of this object into a send email function.
The problem is that I need to be able to pull data from the selected sheet dynamically (the "Miranda" sheet in this case.) In other words, when a user selects the "Yes" option in column I of the Miranda sheet, the first thing this script needs to do is pull the values of the variables at the top of the for loop within the same row that the user selected "Yes." Then, I'm pushing that data to a custom object to be called later.
It's apparent to me, that I'm doing it wrong. There's, at least, something wrong with my loop. What have I done? :)
EDIT:
After reviewing the suggestion by VyTautas, here's my attempt at a working loop:
for (var k = 0; k < dataMir.length; k++) {
var mirColI = dataMir[k][8];
var mirRefDesc = dataMir[k][2];
var mirAdminActions = dataMir[k][3];
var mirDates = dataMir[k][4];
if (mirColI === "Yes") {
var activeRowMir = mirColI.getActiveSelection.getRowIndex();
//Pull selected values from the active row when Yes is selected
var mirRefDescRange = getMirSheet.getRange(activeRowMir, mirRefDesc);
var mirRefDescValues = mirRefDescRange.getValues();
var mirAdminActionsRange = getMirSheet.getRange(activeRowMir, mirAdminActions);
var mirAdminActionsValues = mirAdminActionsRange.getValues();
var mirDatesRange = getMirSheet.getRange(activeRowMir, mirDates);
var mirDatesValues = mirAdminActionsRange.getValues();
var mirHoldingArray = [mirRefDescValues, mirAdminActionsValues, mirDatesValues];
//Push mirHoldingArray values to holdingData
holdingData.refDesc = mirHoldingArray[0];
holdingData.adminActions = mirHoldingArray[1];
holdingData.dates = mirHoldingArray[2];
}
}
Where did all that whitespace go in the actual script editor? :D
You already correctly use .getValues() to pull the entire table into an array. What you need to do now is have a for loop go through dataMir[k][8] and simply fetch the data if dataMir[k][8] === 'Yes'. I also feel that it's not quite necessary to use for (var k in dataMir) as for (var k = 0; k < dataMir.length; k++) is a lot cleaner and you have a for loop that guarantees control (though that's probably more a preference thing).
You can also reduce the number of variables you use by having
holdingData.selectedData = mirData[k]
holdingData.refDesc = mirData[k][2] //I assume you want the 3rd column for this variable, not the 4th
holdingData.adminActions = mirData[k][3] //same as above
remember, that the array starts with 0, so if you mirData[k][0] is column A, mirData[k][1] is column B and so on.
EDIT: what you wrote in your edits seems like doubling down on the code. You already have the data, but you are trying to pull it again and some variables you use should give you an error. I will cut the code from the if, although I don't really see why you need to both get the active sheet and sheet by name. If you know the name will be constant, then just always get the correct sheet by name (or index) thus eliminating the possibility of working with the wrong sheet.
var titleMirRows = 1; // First row of data to process
var numRowsMir = getMirSheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(titleMirRows + 1, 1, numRowsMir - titleMirRows, 26); // might need adjusting but now it will only get as many rows as there is data, you can do the same for columns too
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k = 0; k < dataMir.length; k++) {
if (dataMir[k][7] === 'Yes') { //I assume you meant column i
holdingData.refDesc = dataMir[k] //this will store the entire row
holdingData.adminActions = dataMir[k][3] //this stores column D
holdingData.dates = dataMir[k][4] //stores column E
}
}
Double check if the columns I have added to those variables are what you want. As I understood the object stores the entire row array, the value in column called Administrative Actions and the value in column Dates/Periods if Applicable. If not please adjust accordingly, but as you can see, we minimize the work we do with the sheet itself by simply manipulating the entire data array. Always make as few calls to Google Services as possible.
I've posted a couple questions over the last few days regarding a Google Apps Script Web App I'm working on. So far Serge on here has been super helpful. The post on that script is here.
I'm looking to add a column in that script to generate a uniqueID counter, so that when a new form is submitted, the counter will see previous uniqueID and increment by 1. Idea would be blank spreadsheet with a header row. The script runs on form submit, see A2 is blank and inserts 1. Next submitted form will see 2 in A2 add 1 and put 3 in A3 and so on.
Right now I'm working on just the counter add 1 to the number in the cell. I know as I develop this piece I'll need to grab last row, then find cell value and add to, but beacuse I'm new at GAS, i'm teaching myself as I go, so I"m trying to build slowly to, well you get the idea.
Here is a basic start and I wanted to get some feedback if this is a right direction to start..
function counter() {
//Grabs the range and pinpoint cell
var ssRange = SpreadsheetApp.getActiveSheet().getRange(1,1,1,1);
//Grab the cell value of A1
var value = ssRange.getValue();
//if statement to check value in A1 and if it is null, undefined, false, 0, NAN
if (!value) {
//if the cell is null, undefined, false, 0, NAN sets value to 1
var setCell = ssRange.setValue(1);
}
//if not updates the cell by getting the value and adding 1 to it
else {
var updateCell = ssRange.setValue(value + 1);
}
}
EDIT (updated code to use Phil Bozak tip of .setProperty & .getProperty
I'm still testing on how this code handles if the MYID that inserted into the spreadsheet gets manually changed as you mentioned. I need to ensure the MYID stay unique. Have to think on that.. Here is the code.. Any feedback is appreciated!
function counter2() {
//Get the spreadsheet, range, and value of first cell in range
var sSheet = SpreadsheetApp.getActiveSheet();
var ssRange = sSheet.getRange(1,1,1,1);
var ssValue = ssRange.getValue();
var setUniqueID = ScriptProperties.setProperty("MYID",1);
var getUniqueID = ScriptProperties.getProperty("MYID");
//will set value of first cell to 1 if null, undefined, false, 0, NAN etc.. (runs in if //statement)
var setOne = ssRange.setValue(getUniqueID);
if (!ssValue) {
setOne;
}
else {
//This goes back and get the range, lastrow of range, and value of first cell in range of last row & adds 1 to that value
var setLast = sSheet.getRange(lastRow+1,1,1,1).setValue(getUniqueID + 1);
setLast;
}
}
Another way to be sure to get a value that is always the last count (the highest value) as counter in a column is like that : (comments in code)
I had this idea when you said that values in column A could possibly be modified manually... in this case it is the only approach that avoids possible duplicates (it does not prevent having unused values though... (but this could be implemented too if necessary)
function OnForm(){
var sh = SpreadsheetApp.getActiveSheet()
var startcell = sh.getRange('A1').getValue();
if(! startcell){sh.getRange('A1').setValue(1);return}; // this is only to handle initial situation when A1 is empty.
var colValues = sh.getRange('A1:A').getValues();// get all the values in column A in an array
var max=0;// define the max variable to a minimal value
for(var r in colValues){ // iterate the array
if(Number(colValues[r][0]>max)){max=colValues[r][0]};// get the highest numeric value in th column, no matter what happens in the column... this runs at array level so it is very fast
}
max++ ; // increment to be 1 above max value
sh.getRange(sh.getLastRow()+1, 1).setValue(max);// and write it back to sheet's last row.
}
Ok.. I've updated my code abit and running into an issue I'm hoping someone can help with. The script will see that the spreadsheet is blank and insert a 1 into A1. On next run it will see that 1 is in A1 and add 1 and insert 2 in A2. From then on it just keeps putting 2 in each row over and over.. Here is the code.. not sure why its doing that and not adding 1 each to and increasing the count.
function counter2() {
var sSheet = SpreadsheetApp.getActiveSheet();
var ssRange = sSheet.getRange(1,1,1,1);
var ssValue = ssRange.getValue();
var setOne = ssRange.setValue(1);
var lastRow = sSheet.getLastRow();
var setLast = sSheet.getRange(lastRow+1,1,1,1).setValue(ssValue + 1);
if (!ssValue) {
setOne;
}
else {
setLast;
}
}
EDIT 1
Updated code... The first run on blank spreadsheet fills in A1 = 1 and A2 = 2. It should only put in a 1 in cell A1 and then increment after that. It works fine when running a second time and on. thoughts? What am I missing? function counter2() {
//Get the spreadsheet, range, and value of first cell in range
var sSheet = SpreadsheetApp.getActiveSheet();
var ssRange = sSheet.getRange(1,1,1,1);
var ssValue = ssRange.getValue();
//will set value of first cell to 1 if null, undefined, false, 0, NAN etc.. (runs in if statement)
var setOne = ssRange.setValue(1);
//This goes back and get the range, lastrow of range, and value of first cell in range of last row & adds 1 to that value
var lastRow = sSheet.getLastRow();
var startValue = sSheet.getRange(lastRow,1,1,1).getValue();
var setLast = sSheet.getRange(lastRow+1,1,1,1).setValue(startValue + 1);
if (!ssValue) {
setOne;
}
else {
setLast;
}
}
Edit 2 - Fixed
Here is the updated code that now appears to work fine. I moved the variables "lastRow, startValue, setLast" into the else statement for the second statement. Why would that matter? is it because it runs the variables as it gets to it and by wrapping in the if / else {} brackets it only executes if that statement is run?
function counter2() {
//Get the spreadsheet, range, and value of first cell in range
var sSheet = SpreadsheetApp.getActiveSheet();
var ssRange = sSheet.getRange(1,1,1,1);
var ssValue = ssRange.getValue();
//will set value of first cell to 1 if null, undefined, false, 0, NAN etc.. (runs in if statement)
var setOne = ssRange.setValue(1);
if (!ssValue) {
setOne;
}
else {
//This goes back and get the range, lastrow of range, and value of first cell in range of last row & adds 1 to that value
var lastRow = sSheet.getLastRow();
var startValue = sSheet.getRange(lastRow,1,1,1).getValue();
var setLast = sSheet.getRange(lastRow+1,1,1,1).setValue(startValue + 1);
setLast;
}
}
You can alternatively use ScriptProperties to manage the unique ID. It's a little bit more reliable and a lot easier to work with.
Reliability note: Suppose your spreadsheet somehow was out of order, and your had IDs of [1,2,3,5,4]. Your script would then insert 5 as the next ID. ScriptProperties does a better job of keeping track of the maximum number.
> Sample Usage
var uid = ScriptProperties.getProperty("last_unique_id")+1;
ScriptProperties.setProperty("last_unique_id",uid); //the current UID is now the "last UID"
//do something with the unique_id
You will, of course, have to set the property initially. I recommend you do this with a little function that you run from the menu.
function setUIDOnce() {
ScriptProperties.setProperty("last_unique_id",1);
}