Writing a string of multiple date / time to a single cell - google-apps-script

I have an array of a couple (the array is up to 10) date/time that I want to write to a spreadsheet using getRange().setValues(). I'm converting the array to a string and it looks correct in Logger.
[Mon Feb 02 14:01:00 GMT-06:00 2015, Tue Feb 02 01:00:00 GMT-06:00 2016, , , , , , , , ]
When I try to write the string to a single cell in a sheet:
target6.setValues(source_range6_values);
I get this error:
Incorrect range width, was 10 but should be 1 (line 84, file "Code")
Edited 4/28/2014 adding entire script:
/**
* Copies source range and pastes at first empty row on target sheet
*/
function CopyIt(){
//Establishing source and target sheets
var source_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var target_spreadsheet = SpreadsheetApp.openById("0AhCv9Xu_eRnSdHpLTkc0d1ZURUtyTU9oRjdFbmpMUFE");
// Get source and target sheets - can be the same or different
var sourcesheet = source_spreadsheet.getSheetByName("Form Responses");
var targetsheet = target_spreadsheet.getSheetByName("Work_Orders");
//Get row of last form submission
var source_last_row = sourcesheet.getLastRow();
// Check for answer to Do you need a Flyer Created? If No, end now. If Yes, continue.
var check = sourcesheet.getRange("T"+(source_last_row)).getValue();
if (check == 'Yes') {
//Pulling date(s) from the users form entry (source sheet) into an array
var daterange = sourcesheet.getRange("H"+source_last_row+":Q"+source_last_row);
//Getting the values of the array
var classDate = daterange.getValues();
//changing the array values to a string
classDate.toString();
//Building a new variable with the string to be inserted below in the target sheet
var source_range6_values = classDate;
//source_range6_values.toString();
Logger.log(classDate[0]);
// Get the last row on the target sheet
var last_row = targetsheet.getLastRow();
//Setting the target cell in the Marketing Work Order sheet
var target6 = targetsheet.getRange("U"+(last_row+1));
// Aadding a new row in the target sheet
targetsheet.insertRowAfter(last_row);
//Inserting the values of source_range6_values into the target sheet. Unfortunately it does not enter the data into the same field and it's in military time.
target6.setValue(source_range6_values);
Logger.log(source_range6_values);
}
}

To give a correct answer for your question, i guess i need to know how you get the value of source_range6_values.
One quick guess is you might want to use target6.setValue instead of target6.setValues since you want to write the data into one cell only...

A quick & dirty way would be to replace the commas(with spaces):
source = String(source_range6_values).replace("," , " ");
I've had fun with GAS and variables. Casting it as a String should let you use the string functions on it. If that doesn't work can you share a mock-up of your sheets so I can take a look?
edit:
I had to play around with it a bit, seems google's version of .replace() only replaces the first instance (and doesn't allow .replaceAll() ).
I edited your code starting on line 23:
//Getting the values of the array
var classDate = daterange.getValues().toString();
//Building a new variable with the string to be inserted below in the target sheet
//Google has bugs, .replace() seems to only replace the first instance
//-while {} loop replaces all of them
while (!classDate.equals(classDate.replace("," , " "))) { classDate = classDate.replace("," , " "); };
var source_range6_values = classDate;
All the dates are in one cell if you change only those lines (and no errors).

I appreciate the help you two have given me trying to answer this question. #swimmingwood fixed the actual capture of the data into a string, but it left commas and when I inserted it into the target sheet, it wrote it to multiple cells with an error. It did write to the sheet but the error had you use a CTRL-E (inside the taget sheet) to complete the insert and wrote them into separate cells.
#MickATX suggested the code to replace the commas in the string with a space, which would be fine, but apparently he discovered a Google scripting problem that would only allow for the first comma to be replaced and ignore the rest. Great knowledge never-the-less.
I ended up using a formula in an addition cell in the source sheet that looked like this:
=ArrayFormula(CONCATENATE(REPT(TEXT(H2:Q2,"mm/dd/yyyy hh:mm a")&CHAR(10),H2:Q2>0)))
This formula wrote all the date/time entries provided by the form entry into one cell of the source sheet and ONLY the number of entries (1-10). I then wrote that single cell to the target sheet via the script.
Thanks to #swimmingwood and #MickATX for trying to help me, both provided worthy knowledge.

I've read a couple of strange answers here...
If you write an 2D array to a sheet it will obviously be written accross multiple cells... commas are definitely not the issue but the nature of the object is.
Simply convert your array into a string using .toString() or .join() (the latter providing the advantage you can choose the separator to use) and setValue() (without S) at the place you want.
the commas you see in the logger are only typographic representation of array elements separators...
And, last point : the .join() or .toString() methods return new variables, they don't modify the original value so when you write classDate.toString(); you are not doing anything ...
you should write it like this :
classDateAsAString = classDate.toString();
finally your code :
function CopyIt(){
//Establishing source and target sheets
var source_spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var target_spreadsheet = SpreadsheetApp.openById("0AhCv9Xu_eRnSdHpLTkc0d1ZURUtyTU9oRjdFbmpMUFE");
// Get source and target sheets - can be the same or different
var sourcesheet = source_spreadsheet.getSheetByName("Form Responses");
var targetsheet = target_spreadsheet.getSheetByName("Work_Orders");
//Get row of last form submission
var source_last_row = sourcesheet.getLastRow();
// Check for answer to Do you need a Flyer Created? If No, end now. If Yes, continue.
var check = sourcesheet.getRange("T"+(source_last_row)).getValue();
if (check == 'Yes') {
//Pulling date(s) from the users form entry (source sheet) into an array
var daterange = sourcesheet.getRange("H"+source_last_row+":Q"+source_last_row);
//Getting the values of the array
var classDate = daterange.getValues();
var source_range6_values = classDate.join(' & ');// using & as separator for example
// Get the last row on the target sheet
var last_row = targetsheet.getLastRow();
//Setting the target cell in the Marketing Work Order sheet
var target6 = targetsheet.getRange("U"+(last_row+1));
// Adding a new row in the target sheet
targetsheet.insertRowAfter(last_row);
//Inserting the values of source_range6_values into the target sheet. Unfortunately it does not enter the data into the same field and it's in military time.
target6.setValue(source_range6_values);
Logger.log(source_range6_values);
}
}
Now if you want to format the dates in a more civilized way, that should be handled a bit differently... let me know if you still need it / want it.

Related

Get Sheet By Name with an OR statement

I currently have a script that looks into multiple spreadsheets and pulls in data from a sheet contained within depending on user input.
Unfortunately there are times when users add new sheets in and do not follow the correct format for example each sheet should have four numbers, however sometimes a user will hit the space-bar before inputting those numbers.
Then when the script runs it will come back with an error because it can not find the sheet its looking for, so I need to build in a OR function, my code is quite long so I will type out an example of where I need the function below.
But for example if we can not find sheet "1234" I need to look for sheet " 1234"
var ss= getActiveSpreadsheet()
var sheet = '1234';
var altSheet = ' 1234'
var s1 = ss.getSheetByName(sheet)
//if sheet can not be found, I need to find altSheet instead
You can get the full list of sheets using getSheets(), then use the find() and trim() methods to find the correct one.
const s1 = ss.getSheets().find(sheet => sheet.getName().trim() === '1234');

Replacing a range in one G Sheet with same range from a different, but similar G Sheet template

Goal: to clear data from B3:I71 of the active spreadsheet, and replace it with data at B3:I71 of a separate template document I've created. With this code I keep getting an error saying it can't find the specified range.
function menuItem2() {
var destination = SpreadsheetApp.getActiveSpreadsheet(); // Designates current sheet as destination document
var source = SpreadsheetApp.openById('xxxxxxxx'); //Designates source document
var A1Range = source.getRange("ACTIVITY LOG!B3:I71"); // Specifies a range in the source file, in A1 notation
var cell = destination.getRange("B3");
var SData = A1Range.getValues(); // gets the data values from inside this range
destination.getRange(cell).setValues(SData); // sets new values for all cells within target range at destination to the values of the source data
}
Big picture: I keep a weekly appointment log with random notes, times, dates, mileage, and various other metrics that gets all filled out and cluttered up by the end of the week and is quite tedious to reset for the start of the new week without deleting some of the data that I need to carry forward into the next week (most of which is saved outside of the specified range and thus would not be overwritten or deleted).
Code comments I've included may not be representative of what is actually happening here, just to the best of my limited understanding. At what point do I stray from logical convention and can someone please get me back on track?
function menuItem2() {
const dss = SpreadsheetApp.getActiveSpreadsheet();
const dsh = dss.getSheetByName('destination sheet name');//add destination sheet name
const sss = SpreadsheetApp.openById('xxxxxxxx');
const ssh = sss.getSheetByName('ACTIVITY LOG');
const sdt = ssh.getRange("B3:I71").getValues();
dsh.getRange(3,2,sdt.length,sdt[0].length).setValues(sdt);//edit had row and column reversed sorry
}

Exception: The starting row of the range is too small - Logs Numbers Why?

I have a basic script to take numbers from a sheet and use them to create a range, as well as using the last column function. I have had the error range is too small for the posting range.
When I log the output for both the column and row numbers these come out as expected!
I thought initially, it was because one was a last column pull and the other was pulling an integer from the cell in the sheets, as they were coming with decimal places, so I have overcome this with the conversion to number and then removing the decimals with the .tofixed() but this does not work either. Any ideas?
function weeklyData() {
var sourcess = SpreadsheetApp.openById('1B93Oq2s9Nou5hVgOb3y3t15t9xnqRMBnrYkAed-oxrE'); // key of source spreadsheet
var sourceSheet = sourcess.getSheetByName('Measures & Answers'); // source sheet name - change to your actual sheet name
var lr = Number(sourceSheet.getRange(2,3).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow()).toFixed(0);
for(var i=0;i<lr+1;i++){
var dataValue = sourceSheet.getRange(i+2,3).getValue(); //This weeks numbers to update into table
var rowdataRange = sourceSheet.getRange(i+2,4).getValue(); //The row that the data needs to be pasted
var rowformat = Number(rowdataRange);
var row = rowformat.toFixed(0);
var pasteSheet = sourcess.getSheetByName('WHERE DATA ENDS UP'); // Data is to be pasted - change to your actual sheet name
var pasteColumn = pasteSheet.getRange(12,12).getDataRegion(SpreadsheetApp.Dimension.COLUMNS).getLastColumn()+1;
var column = pasteColumn.toFixed(0); // Column that is free for this weeks data
var pasteRange = pasteSheet.getRange(row,column,1,1);
Logger.log(pasteRange);
// pasteRange.setValue(dataValue);
}};
Your script works fine for me. I suspect this is an example script you've adapted from somewhere and trying to apply it to your data structure?
The reason you are getting the error is probably because the data in column 4 of your source sheet is not of number format? Either change your data or change the following line to the column containing numeric values.
var rowdataRange = sourceSheet.getRange(i+2,4).getValue();
This script is poorly written for this particular use.
You might want to check your Spreadsheet since it has lots of random values at random ranges.
When you use the following command Logger.log(pasteSheet.getLastColumn()); the number returned is 3753: which means that that is the next available column at which your data will be pasted.
The error message you were getting is due to the fact that the range was incorrect and you were passing wrong values in order to access it, which was most likely because of the values mentioned above.
Moreover, after cleaning all the unnecessary data, you can make use of the below script.
Snippet
function weeklyData() {
var spreadsheet = SpreadsheetApp.openById("ID_OF_THE_SPREADSHEET");
var sourceSheet = spreadsheet.getSheetByName("Measures & Answers");
var pasteSheet = spreadsheet.getSheetByName("WHERE DATA ENDS UP");
var valsToCopy = sourceSheet.getRange(2, 3, sourceSheet.getLastRow(), 1).getValues();
var rowsAt = sourceSheet.getRange(2, 4, sourceSheet.getLastRow(), 1).getValues();
var column = pasteSheet.getRange(12,12).getDataRegion(SpreadsheetApp.Dimension.COLUMNS).getLastColumn()+1;
for (var i = 0; i < valsToCopy.length; i++)
if (rowsAt[i][0] != "")
pasteSheet.getRange(parseInt(rowsAt[i][0]), parseInt(column)).setValue(valsToCopy[i][0].toString());
}
Explanation
The above script gathers all the data that needs to be copied as well as the rows associated with it. In order to make sure you don't end up using inappropriate values for the ranges, an if condition has been placed to make sure the value is not empty.
Reference
Sheet Class Apps Script - getLastColumn();
Sheet Class Apps Script - getRange(row, column, numRows, numColumns);

Find and replace script (Google-apps) for Content grouping

Some of you may be familiar with content grouping in Google Analytics, which basically lets you group any number of URL's in user-specified groups (this is useful for analyzing pages that belong together all at the same time). I'm working on a script to take that to the next level and use it in Google Sheets as well.
Goal: have a working script that rewrites URL's and gives them another name, regardless of whether it uses upper or lower cases in the URL.
So far I have this:
function onOpen() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("a1:a10000");
var to_replace = /.*example.*/;
var replace_with = "TEST";
var to_replace2 = /.*another-example.*/;
var replace_with2 = "TEST-Nr2";
replaceInSheet(sheet,range, to_replace, replace_with);
replaceInSheet(sheet,range, to_replace2, replace_with2);
}
This script works in the sense that it rewrites URL's with 'Example' in it to 'Test' and it rewrites 'Another-example' into TEST-Nr2.
However, the final script will probably have thousands of URL's that will need to be rewritten. Furthermore, some URL's have uppercases in them, which I want to ignore and just rewrite.
All of the above leads me to two questions:
How can I write the script in such a way (with regular expressions for example?) that I won't have a Googleplex number of To_replace's and replace_with's?
How can I make my to_replace variables case-incensitive?
If any more information is needed on this matter I will gladly provide so.
Kind regards,
JNeu
Somehow you know the patterns and the replacement values, yes? You need to impart that knowledge to your script.
The simplest way is to read it from a spreadsheet, e.g. on some sheet in some workbook, you have 1 column with the pattern, and another column with the replacement. Then you just read that data in (Range#getValues()), and then iterate that array to process your data range. Note that the pattern you store in the sheet should not include the literal constructor slashes, i.e. you'd want \d{1,3} and not /\d{1,3}/ in the cell.
Example:
function processAll() {
const source = SpreadsheetApp.openById("id of the spreadsheet with pattern - replacement data"),
info = source.getSheetByName("some sheet name")
.getDataRange().getValues();
const databook = SpreadsheetApp.getActive(),
sheet = databook.getSheetByName("name of the sheet with data to process");
if (!sheet) return; // sheet with that name doesn't exist.
const range = sheet.getRange(1, 1, sheet.getLastRow(), 1);
info.forEach(function (row) {
// Create case-insensitive pattern from the string in Column A, e.g. \d{1,3} and NOT /\d{1,3}/
var pattern = new RegExp(row[0], "i");
var repl = row[1]; // replacement text from Column B
replaceInSheet(sheet, range, pattern, repl);
});
}
Additional Reading:
RegExp
Array#forEach

Update named ranges without breaking formula

I have a spreadsheet with many formula referencing named ranges.
The spreadsheet has a script associated with it in a custom menu that imports updated data which can increase the length of the data needed in each range. Once the data are imported the script updates the named ranges to be the length of the new data.
Here is the code block that imports new data and then updates the named ranges:
// add the CSV menu. Might change this to be an automatic update base don date
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var csvMenuEntries = [{name: "Update Data", functionName: "importFromCSV"}];
ss.addMenu("Update", csvMenuEntries);
}
function importFromCSV() {
var file = DriveApp.getFilesByName("double_leads_data.csv");// get the file object
var csvFile = file.next().getBlob().getDataAsString();// get string content
var csvData = Utilities.parseCsv(csvFile);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('DataImport'); // only add data to the tab DataImport to prevent overwriting other parts of the spreadsheet
sheet.getRange(2,1, csvData.length, csvData[0].length).setValues(csvData);// write to sheet in one single step. Start at row 2 (getRange(2... )
SpreadsheetApp.getUi().alert('Data Updated');
//now update the named ranges if they have changed in length
var openEnded = ["business_unit", "channel", "date", "leads", "medium", "region" ];
for(i in openEnded) {
var r = ss.getRangeByName(openEnded[i]);
var rlr = r.getLastRow();
var s = r.getSheet();
var slr = s.getMaxRows();
if(rlr==slr ) continue; // ok as is-skip to next name
var rfr = r.getRow();
var rfc = r.getColumn();
var rnc = r.getNumColumns();
var rnr = slr - rfr + 1;
ss.removeNamedRange(openEnded[i]);
ss.setNamedRange( openEnded[i], s.getRange(rfr, rfc, rnr, rnc ));
}
}
All works well - the data import and the named ranges update. However, after the update all the formula referencing the named ranges break and show #REF where they previously referenced the corresponding named range.
Reading some documentation here there is a sentence
When you delete a named range, any formulas referencing this named
range will no longer work. However, protected ranges that reference a
named range will swap out the named range for the cell values
themselves and continue to work.
I'm not really sure what that means. If I use a protected range instead will it all work? I tried editing the code above? I read about getProtections() here so tried making a small edit:
var openEnded = ["businessunit2", "date2", "leads2", "medium2", "region2", "saleschannel2" ];
var openEnded = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE)
I didn't really expect this to work but was worth a try.
Is there a solution here? How can I update a named range with a script without breaking existing formula which references those ranges? Will using getProtections() lead to a solution or is that just a diversion?
Use INDIRECT("rangeName") in formulas instead of just rangeName. The only way to extend the range programmatically is by removing it and then adding it back with a new definition. This process breaks the formula and returns #ref instead of the range name.
=sum(indirect("test1"),indirect("test3"))
This is a messy and should be unnecessary workaround. If you agree please star the item in the issue tracker.
https://code.google.com/p/google-apps-script-issues/issues/detail?id=5048
the detail you mention about protected ranges wont help you. That refers to the protected range definition in their details pane. You can define a protected range to be a named range and if the named range is deleted it won't break the protected range definition.i also ran into this a while ago and consider it to be a serious bug in their named ranges api. Its ridiculous that their api doesnt have a way to modify them (instead of deleting and recreating). I mean obviously if we use named ranges is because we expect them to change. Sorry for the rant but this is a very old issue that is still broken.
edit: see
https://code.google.com/p/google-apps-script-issues/issues/detail?id=1040 (i'm #7 there from 1.5 years ago)
and
https://code.google.com/p/gdata-java-client/issues/detail?id=196 (im #4 and just added #5 there)
Please star both.
formulas that reference the NamedRange are being broken by the line
ss.removeNamedRange(openEnded[i]);
I believe you can simply omit this line, and go directly to
ss.setNamedRange( openEnded[i], s.getRange(rfr, rfc, rnr, rnc ));
this approach appears to be working for me in a GAS script that adds a column to a NamedRange in Google Sheets. Formulas in other cells reference this named range and are not broken when my script executes
I read the three issue tracker postings and I understand the concern is generating duplicate entries in the set of named ranges. So far I have not seen this behavior so perhaps this bug was fixed.
The following code will update a named range without deleting it or create the range if it doesn't exist.
function fixNamedRange (ss, name, range) {
var ssNamedRanges = ss.getNamedRanges();
var ssRangeNames = ssNamedRanges.map (function (ssRange) {
return ssRange.getName();
}
);
var myRange = ssNamedRanges[ssRangeNames.indexOf(name)];
if (myRange) {
return myRange.setRange(range);
} else {
ss.setNamedRange(name, range);
return -1;
}
}
Edited to employ .map method instead of iterating through array of named ranges.