Writing an array to a spreadsheet - javascript

I've written some script that reads an array (of Responses - multiple rows and columns) from a spreadsheet sheet, extracts data from each row into one or two lines (Entries) which are pushed into a 'main' array, and then writes that 'main' array to a different sheet. Each row of the Responses can contain one or two Entries - shown by a marker value.
Works well except that only the last Entry is pasted, the correct number of rows, to the second sheet.
Somewhere I am not placing the value of each object into the Entry array, just a reference to the value, but cannot see where I have gone wrong.
The spreadsheet is at
https://docs.google.com/spreadsheets/d/1x-HO7KIAQ_s7q55opTd2LRpRRo95S1CqeH193RalWS0/pubhtml
and my script is as follows:
function transferResponses() {
// establish sheets
var aFile = SpreadsheetApp.getActiveSpreadsheet();
var aResponseSh = aFile.getSheetByName("Form responses 1");
var aBaseSh = aFile.getSheetByName("Base");
// check number of response
var numResponses = aResponseSh.getLastRow() - 1;
// put responses in an array
var aResponses = aResponseSh.getSheetValues(2, 1, numResponses, 23);
// define other variables to use
var oneEntry = []; // array for a single entry
var aEntries = []; // array of all entries
var aNumber; // increment part of ID
// counters, etc
var iRes;
var iCol;
var iEntry = 0;
for (iRes = 0; iRes < numResponses; iRes++) {
aNumber = iEntry + 101;
oneEntry[0] = "A" + aNumber.toString(); // form and load entry ID
for (iCol = 1; iCol < 12; iCol++) { // load name, surname, address 1, address 2, phone, email
oneEntry[iCol] = aResponses[iRes][iCol]; // and first entry title, classification, size, description, price
}
oneEntry[12] = aResponses[iRes][18]; // load novice status
oneEntry[13] = aResponses[iRes][21]; // load date of entry
oneEntry[14] = aResponses[iRes][22]; // load method of payment
aEntries.push(oneEntry.valueOf()); // push entry (oneEntry) into main aray (aEntries)
iEntry = iEntry + 1; // increment single entry counter
if (aResponses[iRes][12] === "Add another entry") { // check for a second entry on response
aNumber = iEntry + 101;
oneEntry[0] = "A" + aNumber.toString(); // form and load entry ID
for (iCol = 1; iCol < 7; iCol++) { // load name, surname, address 1, address 2, phone, email
oneEntry[iCol] = aResponses[iRes][iCol]; // and first entry title, classification, size, description, price
}
for (iCol = 7; iCol < 12; iCol++) { // and second entry title, classification, size, description, price
oneEntry[iCol] = aResponses[iRes][iCol + 6];
}
oneEntry[12] = aResponses[iRes][18]; // load novice status
oneEntry[13] = aResponses[iRes][21]; // load date of entry
oneEntry[14] = aResponses[iRes][22]; // load method of payment
aEntries.push(oneEntry.valueOf()); // push entry (oneEntry) into main aray (aEntries)
iEntry = iEntry + 1; // increment single entry counter
}
}
lastRow = aBaseSh.getLastRow();
if (lastRow > 1) { // clear all existing data on base sheet
aBaseSh.deleteRows(2, lastRow);
}
aBaseSh.getRange(2, 1, aEntries.length, aEntries[0].length).setValues(aEntries); // paste main array (aEntries)
}

Try this in every Array after each the equals:
oneEntry[iCol] = aResponses[iRes][iCol].slice(0);
or if only one string:
oneEntry[iCol] = aResponses[iRes][iCol].valueOf;
Also, you question is not specific, Writing an array to a spreadsheet is a pretty generic one, and has nothing to do with your error, wich is caused by Array referencing instead of cloning, your question should also be minimized while still reproducing the error.

Related

Google sheets script - Strip extra data from a second line and add it to the first line

I'm trying to build a script in google sheets script that will iterate down a sheet, row by row, and if it encounters an ID number in the first cell of that row that is identical to the one above it, it strips out the data from every cell EXCEPT Column A and B, and appends it to the line above. Ideally, this would work with an indeterminate number of duplicate row IDs, might be 2, might be 3, might be 4.
After stripping out the data I want to keep (eg colums C and onward), I then want to delete the entire contents of the processed duplicate ID row, but I just haven't put that in my script until after it copies the data correctly.
In this example, sheet rows 6, 7 and 8 have identical ID numbers (Column A)
Here is the result I'm trying to get:
And here is the result I'm getting:
I've tried a number of different ways, and torn down and rebuilt my script a couple of times without getting the result I want:
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,4,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-1,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
I'm not quite sure where I'm going wrong. Does anyone have any suggestions? Thanks!
(Also I'm still just a beginner with this so if I've overlooked anything obvious or taken the wrong approach to do this, I apologize!)
The results for the questioner's code in the case of copied data arise from a convoluted loop. In essence, though duplicates were identified, there was a mis-counting to assign the copied data to the correct rowID. So far as clearing data, no provision was included.
The following code works to meet the questioner's goals, though it is far from perfect.
At present, the recalculation of the "last column" after copy each duplicate is an absolute rather than than a row-based figure. So, if a duplicate was detected for, say, ID=3, the data would be copied to column 12 rather than column 6. This requires the addition of a simple dupID row counter.
The second factor is the calculation of the last column in the spreadsheet.
var dataRange = mainSheet.getRange(startRow, 1,Rowlast+1,120);
The questioner used 120 columns; and I have retained that number simply for the sake of consistency. The questioner should re-assess whether this is excessive.
function ejb_so_5284922701() {
var ss = SpreadsheetApp.openById("<< insert questioners spreadsheet ID>>");
var mainSheet = ss.getSheetByName("<< insert questioner's sheet name >>");
var startRow = 5;
// calculate the last row containing data
var Rowvals = ss.getRange("A5:A").getValues();
var Rowlast = Rowvals.filter(String).length; //6
Logger.log("last row = " + Rowlast); // DEBUG
// calculate the last column containing data
var cell = mainSheet.getRange("A5"); //or however you determine "cell"
var drCol = mainSheet.getDataRange().getLastColumn();
Logger.log('getLastColumn = ' + drCol); //DEBUG
for (var i = drCol; i >= 1; i--) {
if (mainSheet.getRange(cell.getRow(), i).getValue() != "") {
break;
}
}
var lastColumn = i;
Logger.log("Last column with data = " + lastColumn); //DEBUG
var setrgh = mainSheet
// numColumns neds to be reviewed
var dataRange = mainSheet.getRange(startRow, 1, Rowlast + 1, 120); //rowStart, columnStart, row count, column count, the column count needs to be large enough to encompass all your ancillary data
// start row = 5, 1= column A, 4, rows, 120, columns
Logger.log("startRow = " + startRow + ", and the datarange = " + dataRange.getA1Notation()); //DEBUG
var data = dataRange.getValues();
Logger.log("length of data =" + data.length); //DEBUG
var lastid = 0;
for (i = 0; i < data.length; i++) {
if (i == 0) {
// if this is the first row, then assign anything but zero to last id
lastid = 100;
Logger.log(" this is the first row; set last id to 100");
}
var thisid = data[i][0];
// evaluate whether this is a duplicate ID
if (thisid == lastid) {
// this is a dup
Logger.log("i=" + i + ". This is a dup" + ", name is " + data[i][2]); //DEBUG
var stufftocopyrange = mainSheet.getRange(startRow + i, 3, 1, 3);
var stufftocopy = stufftocopyrange.getValues();
Logger.log("the range to copy is " + stufftocopyrange.getA1Notation()); //DEBUG
var targetrange = mainSheet.getRange(startRow + lastid - 1, lastColumn + 1, 1, 3);
targetrange.setValues(stufftocopy);
lastColumn = lastColumn + 3;
var duprange = mainSheet.getRange(startRow + i, 1, 1, 5);
Logger.log("the range to clear is " + duprange.getA1Notation()); //DEBUG
duprange.clearContent();
} else {
// no dup
//assign lastid value
var lastid = thisid;
Logger.log("i=" + i + ". No dup. Last id set to " + lastid); // DEBUG
} // if
} // end for loop
}
BEFORE
AFTER
The solutions previously posted didn't quite get the result I needed, however I managed to cobble together something that works for my purposes. It expects to see data in the format like:
And turn it into something like this:
Where it uses duplicate ID numbers (with an indeterminite number of duplicates) to pull only certain columns of data from the duplicate lines and append it to the first line.
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,3,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-2,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
Hopefully someone else who wants to do a similar thing can make use of this too.

Google Apps Script for creating normalised Data from unnormalised Data in Google Sheets

In a Google Sheet, I have 3 raw data worksheets within one google spreadsheet ('Category1'),('Category2'),('Category3'). These worksheets are constantly updated by people in my business but unfortunately the data isn't in a normailised form to be able to run efficient queries.
I would like to create a script that automatically generates a normailised Output ('Category1 Output'),('Category2 Output'),('Category3 Output') of this raw information that automatically updates itself when someone makes a change in the raw tabs.
In the google sheet below, I have provided an example of what one Category needs to look like. 'Category1' worksheet is the raw that is constantly updated by everyone. 'Category1Output' is the final output worksheet that automatically updates itself when an edit is made in the 'Category1' worksheet.
Google Sheet Link
The Questioner has three sheets in a defined columnar layout - essentially several rows per data set and several columns (one for each time period). These sheets are not being replaced, but a summarised version was sought where filtering could be used to focus effectively on relevant data. Thus, each sheet to be translated from columnar format to row-wise format.
The process itself is straightforward. The source data comprised 64 products # 8 data rows per product. The output records were #1,350.
The questioner's code was hung up on the conversion of data to the output format. The use of 8 rows of data per product is important, and the code includes a check that the quotient the total number of rows of data divided by eight is an integer. In addition, the names of the Source sheet and the Output sheet are called by name (getSheetByName) so that the code can be easily applied to any named Input Sheet and any named Output sheet. The only proviso is that both sheets must exists beforehand.
Initial resolution of the questioner's code hiccup was successful and using the methodology of getDataRange and getValues before the loop greatly improved performance. There are two loops; one with a vertical orientation, moving through the rows of data; and the second with a horizontal orientation moving through the time-related columns. However performance was initially very inefficient and the code was timing out before completion.
I modified the code to build a single 2D array and save it to the Output sheet just once at the end of the loops. This had a dramatic effect on performance. Total time to complete dropped from several minutes to less than 5 seconds.
function so5243560403() {
// Build a clean Output Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var SourceSheet = ss.getSheetByName("Category2");
var Target = ss.getSheetByName("Category3Output");
// Various variables
var SourceHeaderRow = 9;
var RowsPerProduct = 8;
var ProductLengthTruncate = 11;
var SourceArray = [];
var i = 0;
var w = 0;
// get the bottom of the column
var ColAvals = SourceSheet.getRange("A" + (SourceHeaderRow + 1) + ":A").getValues();
var ColAlast = ColAvals.filter(String).length;
//Logger.log("Last row in column A with data"+ColAlast); //DEBUG
var NumberofProducts = ColAlast / RowsPerProduct;
var lastRow = SourceSheet.getLastRow();
// Count the products and confirm eight rows each
var prodtest = isInt1(NumberofProducts);
if (!prodtest) {
// Logger.log("NOT an integer!");
SpreadsheetApp.getUi().alert("Number of Rows divided by rows by Product isn't an integer");
return false;
}
// Get data to clear Target ready for new data
var TargetlastRow = Target.getLastRow();
var TargetlastColumn = Target.getLastColumn();
// clear the content before re-building
Target.getRange(2, 1, TargetlastRow, TargetlastColumn).clear({
contentsOnly: true
});
// Get ALL the data on the SourceSheet
var SourceRange = SourceSheet.getDataRange();
var SourceValues = SourceRange.getValues();
// create loop for rows of data; first row of data in array=9
for (i = SourceHeaderRow; i < (SourceHeaderRow + ColAlast); i = i + 8) {
// create loop for weeks (Week 1=Col5, Week 2=Col6... Week 52=Col56, etc) (actual column numbers are +1)
for (w = 1; w < 53; w++) {
// Test to see whether there's a value for Display; the only field ALWAYS populated
if (SourceValues[i + 1][w + 4]) {
// Get Product and data fields
var Prodlen = SourceValues[i][3].length;
var prodedit = SourceValues[i][3].substring(11, (SourceValues[i][3].length));
var product = prodedit.trim();
var catalogue = SourceValues[i][w + 4];
var display = SourceValues[i + 1][w + 4];
var ESP = SourceValues[i + 3][w + 4];
var mechanic = SourceValues[i + 6][w + 4];
var join1 = product+" | "+display+" | "+mechanic;
var join2 = display+" | "+product+" | "+mechanic;
// Start building an array
SourceArray.push([w, product, catalogue, display, ESP, mechanic,join1,join2]);
} // end if data exists - process this week
} // end w - this week loop
} // end i - this row loop
// Copy the data from the array to the Target sheet
// count number of rows
var SourceArraylen = SourceArray.length;
// first row is #2, allowing for header row
// first column = A
// number of rows = length of array
// number of columns = 6 (the fields puched to the array
var TargetRange = Target.getRange(2, 1, SourceArraylen, 8);
// set the array values on the Target sheet
TargetRange.setValues(SourceArray);
}
function isInt1(value) {
return !isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10));
}
UPDATE
The second element of the Questioner's code deals with updating data to an "Output Sheet" as changes are made to "Category" sheets. The questioner's code for the update was OK, but was missing the translation of the source range on the Category Sheet to establishing the target range on the Output sheet.
The solution involves a rubric based on mathematical number sequence. In this case, the mathematical sequence is the row numbers for products on the source sheet; each product occupies 8 rows, and the first row is #10, so the sequence is 10,18,26,34....
onEdit returns the range of the changed cell and getRow and getColumn can be used to establish the co-ordinates of the changed cell. The challenge is, knowing the actual row number that was changed, to establish what number in the sequence of rows (and hence the product name) that the actual row number represents. It is also extremely unlikely (eight to one) that the changed row will coincide with the first row for a product number.
So it is necessary to apply the algorithm for mathematical sequences - twice. The formula to determine the nth number in a sequence of numbers is An = A1 + (D x(n-1)), where A1 is number for the first row (in our case, 10), D= the difference between each number in the sequence (in our case, 8), and n= the number in the sequence (in our case, the changed row number).
The first pass is to establish the position number in the sequence of numbers(product groups) represented by the actual changed row. It is very likely that the outcome is not an integer, i.e. it does not coincide with the first row for a Product group. So, the result is rounded down to the nearest integer, and the algorithm is processed a second time.
However this time we know the position of the sequence number, and we solve to find the value of the number. In this case, the formula is ((An-A1)/D)+1. This will return the row number in the Source sheet corresponding to the first row for the relevant Product group. We use this to identify what type of field was changed (Category, Display, etc).
The column number indicates the week number. Week 1 starts in Column F, so get column enables us to establish whether the change took place in a week column (or whether it was to left of Column F). If to the left, then "not my problem", if in F or higher, then it needs to be noted.
Lastly, we do a getRangeValue for the Target sheet and look for a match of the week number in Column A AND the truncated Product name in column B. This provides the co-ordinates to setValue for the new value tracked from OnEdit.
function OnEdit(e) {
// Update relevant Outputsheets on changes in Category sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Establish variables
var s1 = "Category1";
var s2 = "Category2";
var s3 = "Category3";
var tsuffix = "Output";
//Logger.log("Sheet information");//DEBUG
//Logger.log("The sheets to track are s1= "+s1+", s2 = "+s2+", and s3 = "+s3+", and the Output suffix is "+tsuffix+". For example s1output = "+s1+tsuffix);// DEBUG
var TargetSheet = "";
var weekscolumnstart = 6; // Column F
var startrow = 10; // applies to the Source sheet
var rowsperProduct = 8; // applies to the source sheet
var changedfield = 0;
var changedfieldname = "";
var n = 0;
// Collect data from the event
var range = e.range;
var oldValue = e.oldValue;
var value = e.value
var source = e.source;
var sheet = source.getActiveSheet();
var ssname = sheet.getName();
// Logger.log("Range: "+range.getA1Notation()+", old value = "+oldValue+", new value = "+value+", source = "+source+", ss = "+sheet+", sheet name = "+ssname); //DEBUG
// get the co-ordinates of the change
var SourceRow = range.getRow();
var SourceColumn = range.getColumn();
// Logger.log("the Column is "+SourceColumn+", and the Row is "+SourceRow);// DEBUG
// the weeks range to the right, from column F (va = weekscolumnstart). So by knowing the column number of the even, we can calculate the week number that applied to the change.
var weeknumber = (SourceColumn - weekscolumnstart + 1);
switch (ssname) { // the field references are used in a GetValue statement where the column is a reference to a specific column
case s1:
TargetSheet = s1 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
case s2:
TargetSheet = s2 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
case s3:
TargetSheet = s3 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
default:
TargetSheet = 0;
//Logger.log("The change was made in a sheet that we don't need to track.");
} // end switch
// get product and other change information if the change is on a tracked sheet and in a relevant column.
// evalue for the event on a non-relevant sheet or in a non-relevant column
if (TargetSheet == 0 || weeknumber <= 0) {
// do nothing
} else {
//Logger.log("before calculating line number; the TargetSheet is "+TargetSheet);
// The source has eight rows of data per Product; there is no predictability about which one of the eight will be chnaged for a given product.
// However the sequence of all the rows follows a mathenmatical sequence, so by knowing the row, it is possible to determine the product grouping
// And by knowing the product grouping, it is possible to determine the first row of the product group.
//
// The formula for the position of a number n a mathematical sequence is: = an=a1+d(n-1)
// where an = the "nth" number in the sequence (equates to the nth Product); a1 = the start row (var=startrow); d = difference between each group (var=rowsperProduct) and n=the actual row number.
// In the first instance we know the row number from the event data, so we work backwards to solve for the position of that number in the sequence.
//
// 1) calculate the starting row for this product
// 2) (Row number - starting row) divided by rowsperProduct) plus one.
// 3) There's only a one-in eight chance that it is an integer, so round down to get first row of this product sequence
// 4) Then we work forwards; since we know the nth number, we can calculate the row number for the first row for that product.
// 5) starting row plus (rowsperproduct x (seqwuence number minus 1))
// By knowing the first row in the product sequence, and the row number that was chnaged, we can calculate which data set was chnaged.
var productseq = (((SourceRow - startrow) / rowsperProduct) + 1);
var productseqround = Math.floor(productseq);
var productline = (startrow + (rowsperProduct * (productseqround - 1)));
//Logger.log("the row number is "+SourceRow+", but the sequence number for this product is "+productseqround+", and the startrow for this product group = "+productline); //DEBUG
// identify the field that has changed
// Source Row number less Productline
// if 0 = Catalogue
// if 1= Display
// if 3 = ESP
// if 6 = Mechanic
var LineNumber = (SourceRow - productline);
//Logger.log("the calculated Line number = "+LineNumber); //DEBUG
switch (LineNumber) { // the field references are used in a GetValue statement where the column is a reference to a specific column
case 0:
changedfield = 3;
changedfieldname = "Catalogue";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 1:
changedfield = 4;
changedfieldname = "Display";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 3:
changedfield = 5;
changedfieldname = "ESP";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 6:
changedfield = 6;
changedfieldname = "Mechanic";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
default:
//Logger.log("the changed field was none of the above");
changedfield = 0;
} //end switch
} //end if
// OK, let's get this party started..
// evaluate the sheet
if (TargetSheet == 0) {
//Logger.log("Do nothing because it's not on a sheet we need to worry about"); //DEBUG
}
// evaluate the week applying to the change
else if (weeknumber <= 0) {
//Logger.log("whatever was changed wasn't one of the key fields"); //DEBUG
}
//evaluate the changed field
else if (changedfield == 0) {
//Logger.log("Do nothing because it's not a field that we're not worried about"); //DEBUG
}
// looks OK to go ahead
else {
//Logger.log("the field was changed for week# "+weeknumber+", lets find the product");
// trim the Product Code for searching on the Output Sheet
var LongProdName = sheet.getRange(productline, 4).getValue();
var Prodedit = LongProdName.substring(11, (LongProdName.length));
var ShortProdName = Prodedit.trim();
//Logger.log("the Product Name is "+LongProdName+", shortened to: "+ShortProdName);// DEBUG
// test for existence of the TargetSheet
var sheeterror = 1; // use this variable as the canary in the coal mine. Set to 1, prima facie error
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TargetSheet);
if (target != null) { // test for exitentce of target;
sheeterror = 0; // if target sheet exists, then set sheeterror to zero; that is, sound the all clear
}
if (sheeterror != 0) { // now test for a sheet erorr; anything other than zero means there is a problem
SpreadsheetApp.getUi().alert("WARNING#1: \n\n The Output Sheet: " + TargetSheet + " does NOT exist.\n\n Product: " + LongProdName + ", \nWeek: " + weeknumber + ",\nField: " + changedfieldname + ", \nold value = " + oldValue + " \n new value = " + value + ".\n Date: " + (new Date()));
// Logger.log("ERROR: The Outout sheet:" + TargetSheet + " doesn't exist. Data changed on sheet:" + ssname + ", Product: " + LongProdName + ", Week# " + weeknumber + ", Field: " + changedfieldname + ", old value=" + oldValue + ", new value=" + value + ", Date " + (new Date())); //DEBUG
return false;
}
// set the data range for the Output sheet and get the data
var TargetRange = target.getDataRange();
var TargetValues = TargetRange.getValues();
// setup the search string
// Logger.log("target Range = "+TargetRange.getA1Notation()+", search string = '"+ShortProdName+"', week# is "+weeknumber); // DEBUG
// Logger.log("TargetValues length = "+TargetValues.length);
// so lets find a match
var outputmatch = 1; // use this variable as the canary in the coal mine for not finding a match. Set to 1 = prima facie error
for (n = 0; n < TargetValues.length; ++n) {
// iterate row by row and match the week (Column A) and Name (Column B)
//Logger.log("n = "+n+", product = "+ShortProdName+", week = "+weeknumber);
if (TargetValues[n][1] == ShortProdName && TargetValues[n][0] == weeknumber) {
// when we find the result (row number), add plus one to accout for the array starting at zero.
var result = n + 1;
// Logger.log("Found a match, n = "+result); //DEBUG
// create the co-ordinates for the output cell
// row number = result, column = chnagedfield calculated earlier
// Logger.log("update range: row = "+result+", column = "+changedfield); //DEBUG
var updatecell = target.getRange(result, changedfield);
//Logger.log("The update cell is "+updatecell.getA1Notation()); // DEBUG
// Update the cell for the new value
updatecell.setValue(value);
// Fix values for Display/Mechanic if they were updated
if (changedfieldname == "Display") {
var displayvalue = value;
} else {
var displayvalue = TargetValues[n][3];
}
if (changedfieldname == "Mechanic") {
var mechanicvalue = value;
} else {
var mechanicvalue = TargetValues[n][5];
}
// define the join1 parameters
var join1 = TargetValues[n][1] + " | " + displayvalue + " | " + mechanicvalue; // Bundle, Display, Mechanic
// set the range for join 1
var updatejoin1 = target.getRange(result, 7);
// update join1
updatejoin1.setValue(join1);
// define the join2 parameters
var join2 = displayvalue + " | " + TargetValues[n][1] + " | " + mechanicvalue; // Display, Bundle, Mechanic
// set the range for join 2
var updatejoin2 = target.getRange(result, 8);
// update join2
updatejoin2.setValue(join2);
// the outputmatch value to zero
outputmatch = 0;
//Logger.log("The update cell is "+updatecell.getA1Notation()+", and the new value is "+ value); //DEBUG
//Logger.log("SUMMARY: Data changed on sheet:" + ssname + ", saved to Output sheet:" + TargetSheet + ", range: " + range.getA1Notation() + ", Product: " + LongProdName + ", Week# " + weeknumber + ", Field: " + changedfieldname + ", old value=" + oldValue + ", new value=" + value + ", Date " + (new Date())); //DEBUG
return false;
}
} // end for n
if (outputmatch != 0) { // now test for a faliure to update the output sheet; anything other than zero means there is a problem
// create an error message if we were unable to find a match and could not update the output sheet field
SpreadsheetApp.getUi().alert("WARNING#2: There was an unidentified problem.\n\n Output Sheet: " + TargetSheet + " does NOT appear to have been updated.\n\n Product: " + LongProdName + ", \nWeek: " + weeknumber + ",\nField: " + changedfieldname + ", \nold value = " + oldValue + " \n new value = " + value + ".\n Date: " + (new Date()));
return false;
}
} // end if
}

Check whether Google Sheets cells contain values using Google Apps Script

In google app scripts, I am pulling in data (all strings) from a google sheet and then trying to split up the array into separate columns excluding the first row (header row). The data has an arbitrary amount of rows and columns. "projects" are the headers and "tasks" is the data underneath each header.
// load in task names
function LoadTasks() {
var sheet = ss.getSheetByName("Task List");
sheet.activate();
var allTasks = sheet.getDataRange().getValues();
return allTasks;
}
function Analysis(project, tasks) {
var sheet = ss.getSheetByName(project);
sheet.activate;
var taskLength = tasks.length;
var sheetLength = (taskLength*4) + 10;
Logger.log("The Project is " + project + " with task length of " + taskLength + " and task data of: " + tasks);
}
taskData = LoadTasks();
numProjects = taskData[0].length;
// load all project names into single array
for (i = 0; i < numProjects; i++) {
projectNames[i] = taskData[0][i];
}
for (i = 0; i < numProjects; i++) {
project = projectNames[i];
j = 1;
while (taskData[j][i] != null) {
tasks[j-1] = taskData[j][i];
j++;
}
Analysis(project, tasks)
tasks = [];
}
In the very last while loop, how can I check to see if the array value I'm looking at holds a value (does not contain a null, undefined, or blank). My current method gives me the error: Cannot read property "0.0" from undefined. (line 91, file "Code")
***Line 91: while (taskData[j][i] != null) {
The method getValues() returns a double array of values which can be numbers, strings, or Date objects. There are no "null" or "undefined" among the values. Blank cells are represented as empty strings "" and you can detect them by comparison to "".
That said, one should control the index bounds explicitly:
while (j < taskData.length && taskData[j][i] !== "") {
tasks[j-1] = taskData[j][i];
j++;
}

Send emails from two distinct columns using data in google spreadsheets

Good morning.
Very new coder with little background. I need to merge data from a google spreadsheet into an email, without using an add-on. I borrowed this code from a site and I'm able to produce an email but it will only pull from the first email address column. I need to send an email to both the manager and director. Their email address will be stored in two separate columns with unique labels. I can't change the spreadsheet data as the spreadsheet is storing responses pulled from a survey form that is already in progress (example column layout below):
Name / Email Address / Director Name / Director Email Address / Response 1 / Response 2 / etc...
Everything I've researched will send an email from one column, but not two, or a "cc".
Below is the borrowed code. Would very much appreciate any help on how to modify the code to send the "response" data to both the Manager and Director in one email.
kind regards,
KA
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, 15);
var templateSheet = ss.getSheets()[1];
var emailTemplate = templateSheet.getRange("A1").getValue();
// Create one JavaScript object per row of data.
var objects = getRowsData(dataSheet, dataRange);
// For every row object, create a personalized email from a template and send
// it to the appropriate person.
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];
// Generate a personalized email.
// Given a template string, replace markers (for instance ${"First Name"}) with
// the corresponding value in a row object (for instance rowData.firstName).
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = "Data Survey";
MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText);
}
}
// Replaces markers in a template string with values define in a JavaScript data object.
// Arguments:
// - template: string containing markers, for instance ${"Column name"}
// - data: JavaScript object with values to that will replace markers. For instance
// data.columnName will replace marker ${"Column name"}
// Returns a string without markers. If no data is found to replace a marker, it is
// simply removed.
function fillInTemplateFromObject(template, data) {
var email = template;
// Search for all the variables to be replaced, for instance ${"Column name"}
var templateVars = template.match(/\$\{\"[^\"]+\"\}/g);
// Replace variables from the template with the actual values from the data object.
// If no value is available, replace with the empty string.
for (var i = 0; i < templateVars.length; ++i) {
// normalizeHeader ignores ${"} so we can call it directly here.
var variableData = data[normalizeHeader(templateVars[i])];
email = email.replace(templateVars[i], variableData || "");
}
return email;
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects'
// tutorial.
//
//////////////////////////////////////////////////////////////////////////////////////////
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(range.getValues(), normalizeHeaders(headers));
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit(char) {
return char >= '0' && char <= '9';
}
According to the documentation : Mail App, the recipent represents the addresses of the recipients, separated by commas
So in order to send to several recipents, just make a string separated by comma:mike#example.com, mike2#example.com
You could pick the rows you want and do something like this:
MailApp.sendEmail(rowData[0].emailAddress + ',' + rowData[1].emailAdress, emailSubject, emailText);

JavaScript - Pushing a value on an array after clearing it keeps the old values

I'm using JavaScript (prototype and ajax), PHP and MySQL to display some values from a database in textfields and a dropdown list, but I ran into a problem with JavaScript.
Every time a user clicks on an option in a list the values should update. These values are pushed in an array, but even after clearing it or setting its length to 0, the values get pushed to the end of how the array was before.
It all works fine the first time, but after that the array keeps on getting bigger and it adds more and more values to the dropdown list. Here's the code:
function artSelect(art){
//perform an Ajax request
var artRequest = new XMLHttpRequest();
//If the output is returned successfully, add it in the document
artRequest.onreadystatechange = function() {
if (artRequest.readyState==4) {
//Get the text
var text = artRequest.responseText;
//Create an array
var x = new Array();
//Create a variable for storing the position of the last semicolon (the values are seperated by semicolons)
var lastSemCol = 0;
//Split the text by scanning for semicolons and put them in an array
for (var i=0; i<text.length; i++) {
//If the current char is ";"
if (text[i] === ";") {
//Create a substring of the chars between the last ; and current ;
var value = text.substr(lastSemCol + 1, i-lastSemCol-1);
//Add the value to the array
Array.prototype.push(value);
lastSemCol = i;
}
}
//Set the values in the document
$("beschrijving").value = x[1];
$("kleur").value = x[2];
$("voorraad").value = x[3];
$("prijs").value = x[4];
$("srtc").value = x[5];
//Set the departments
for (var i=6; i < Array.prototype.size(x); i++) {
var option = document.createElement("option");
option.text = x[i];
$("afd").add(option);
}
//Clear the array (no effect)
x.clear();
x.length = 0;
x = new Array();
}
};
artRequest.open("GET", "server.php?mode=getArtikel&artikel=" + selected.value.substr(0,7), true);
artRequest.send(null);
}

Categories

Resources