Unique ID counter - javascript

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);
}

Related

Google Sheets: How do I list rows of data copied from another sheet consecutively, in order, and without overwriting current data through scripting?

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

Split sheet based on column

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).

how to change value in a range of cell to a data array in google AppScript?

How to pull data in a range of cell and then push those values into an array of data?
In this code I tried to get those values in range E2:E97 and then push those values to an array such as [E2 value, E3 Value, etc] then set value to database sheet using dataS.getRange(dataS.getLastRow()+1).setValue(value);
but it seems I can't get it done with those code. so any idea to do this?
// Save Data
function saveData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("newform");
var dataS = ss.getSheetByName("newdb");
var dataBaseNameColIndex = 1;
var formDataRange = ['E2:E97'];
var ui = SpreadsheetApp.getUi();
var response = ui.alert(
'Save Data ?',
ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
for (var i = 2; i < formDataRange.length; i++) {
var value = formS.getRange(formDataRange[i], 5).getValue();
dataS.getRange(dataS.getLastRow() + 1).setValue(value);
console.log(saveData);
}
}
}
Instead of trying to copy and paste data cell-by-cell, you may want to do it in one go. That would be more efficient.
So instead of:
var value = formS.getRange(formDataRange[i], 5).getValue();
you could use the following without the for loop:
var allValues = formS.getRange('E2:E97').getValues();
And then post the data to the new range like this:
dataS.getRange(dataS.getLastRow() + 1, 2, allValues.length, allValues[0].length).setValues(value);
I am assuming you want to paste the data into column 2 onwards. Adjust the 2nd value in getRange() above accordingly.
The 3rd and 4th values in getRange() above are the number of rows and columns to pasts.
allValues.length is the number of rows of data. In this case it would be 95.
and allValues[0].length would the number of columns in the top row of the data copied. And in this case it should be one.
I am suggesting this way as you won't need to keep fiddling with the number of rows and columns if you change the copy range dimensions later on.
PS:
dataS.getRange(dataS.getLastRow() + 1).setValue(value);
is missing the column number in getRange()

Looping through column untill empty row, looking up each value in different SS and return row position

I'm trying to accomplish somewhat of a database table editor that edits rows based on a Trans ID.
I've accomplished vlookups and return values with a vlookup similar script I have, but this time I need to return position and not value.
I need help on how to set this script flow up. I've drawn and wrote everything out on a picture, it was a lot easier to write out that way.
This is what I'm starting with and need some suggestions.
I want to return row position instead of value and repeat until hit first empty row as seen in example picture I attached.
function vlookup(sheet, column, index, value) {
var sheet = sheet
var lastRow= sheet.getLastRow();
var data=sheet.getRange(1,column,lastRow,column+index).getValues();
for(i=0;i<data.length;++i){
if (data[i][0]==value){
return data[i][index];
}
}
}
The row number is just the index of the array + 1 as arrays start at 0.
return i + 1;
Heres the script on how I accomplished the task.
function doit(){
var lastrow = gv.glr(gv.invhelper,"A") // global function getting last row of target/loop column
for (var i = 4; i < lastrow+1; i++){ //loop statement
// actions to repeat
var id = gv.invhelper.getRange("A"+i).getValue(); //get id from row
var duradj = gv.invhelper.getRange("D"+i).getValue(); //get billing adjustment from row
var dbidrow = gv.dblkup(id); // global function to lookup id and find row position in other ss
var invstat = gv.dbtimetest.getRange("G"+dbidrow).setValue('TRUE'); // set invoiced status
var billadjust = gv.dbtimetest.getRange("M"+dbidrow).setValue(duradj); // set billing adjustment
}
}

Copy cell values from one google sheet to another based on condition

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();

Categories