I am using a Google Apps Script that pulls the content from a feed in a sheet.
This is the code that I'm using:
function processXML(FeedURL,sheetsFileDestinationURL,rawPasteSheetName,OPT_childNamesArray,OPT_Namespace){
var OPT_childNamesArray = ["link"]; // get only item url from the feed
var GoogleSheetsFile = SpreadsheetApp.openByUrl(sheetsFileDestinationURL);
var GoogleSheetsPastePage = GoogleSheetsFile.getSheetByName(rawPasteSheetName);
if (OPT_childNamesArray){
GoogleSheetsPastePage.getDataRange().offset(1,0).clearContent(); // get all filled cells, omitting the header row, and clear content
}
else {
GoogleSheetsPastePage.getDataRange().offset(0,0).clearContent(); // get all filled cells, INCLUDING the header row, and clear content
}
// Generate 2d/md array / rows export based on requested columns and feed
var exportRows = []; // hold all the rows that are generated to be pasted into the sheet
var XMLFeedURL = FeedURL;
var feedContent = UrlFetchApp.fetch(XMLFeedURL).getContentText(); // get the full feed content
var feedItems = XmlService.parse(feedContent).getRootElement().getChild('channel').getChildren('item'); // get all items in the feed
for (var x=0; x<feedItems.length; x++){
// Iterate through items in the XML/RSS feed
var currentFeedItem = feedItems[x];
var singleItemArray = []; // use to hold all the values for this single item/row
// Parse for specific children (requires names and namespace)
if (OPT_childNamesArray){
for (var y=0; y<OPT_childNamesArray.length; y++){
// Iterate through requested children by name and fill rows
var currentChildName = OPT_childNamesArray[y];
if (OPT_Namespace){
if (currentFeedItem.getChild(OPT_childNamesArray[y],OPT_Namespace)){
singleItemArray.push(currentFeedItem.getChildText(OPT_childNamesArray[y],OPT_Namespace));
}
else {
singleItemArray.push("null");
}
}
else {
if (currentFeedItem.getChild(OPT_childNamesArray[y])){
singleItemArray.push(currentFeedItem.getChildText(OPT_childNamesArray[y]));
}
else {
singleItemArray.push("null");
}
}
}
exportRows.push(singleItemArray);
}
// Parse for ALL children, does not require knowing names or namespace
else if (!OPT_childNamesArray){
var allChildren = currentFeedItem.getChildren();
if (x == 0){
// if looking at first item, create a header row first with column headings
var headerRow = [];
for (var h=0; h<allChildren.length; h++){
headerRow.push(allChildren[h].getName());
}
exportRows.push(headerRow);
}
for (var c=0; c<allChildren.length; c++){
singleItemArray.push(allChildren[c].getText());
}
exportRows.push(singleItemArray);
}
}
// Paste the generated md array export into the spreadsheet
if (OPT_childNamesArray){
GoogleSheetsPastePage.getRange(2,1,exportRows.length,exportRows[1].length).setValues(exportRows);
}
else if (!OPT_childNamesArray){
var maxRangeLength = 0;
var currentRowIndex = 1;
for (var x = 0; x<exportRows.length; x++){
if (exportRows[x].length > maxRangeLength){
maxRangeLength = exportRows[x].length;
}
GoogleSheetsPastePage.getRange(currentRowIndex,1,1,exportRows[x].length).setValues([exportRows[x]]);
currentRowIndex++;
}
}
}
My problem is this:
When I run this code I get:
https://url/115-396/
https://url/115-396/
https://url/115-396/
I need to remove "115-396/".
So I tryed to add this code but didn't work:
...
// Paste the generated md array export into the spreadsheet
if (OPT_childNamesArray){
for (var k = 0; k < exportRows.length; k++) {
var re = '115-396/'
var replacingItem = '';
var URL = exportRows[0].toString().replace(re, replacingItem);
}
GoogleSheetsPastePage.getRange(2,1,exportRows.length,exportRows[1].length).setValue(URL);
}
else if (!OPT_childNamesArray){
...
Edit after #Yuri reply:
// Paste the generated md array export into the spreadsheet
if (OPT_childNamesArray){
for ( k=0; k < exportRows[0].length; k++) {
var re = '115-396/'
var replacingItem = '';
exportRows[0][k] = exportRows[0][k].toString().replace(re, replacingItem);
}
GoogleSheetsPastePage.getRange(2,1,exportRows.length,exportRows[1].length).setValues(exportRows);
}
result:
https://url/
https://url/115-396/
https://url/115-396/
Basically, the regex is applied only to the first url.
How I can make that the regex is applied to all the url's?
Any help?
Thanks
You are using a for to iterate thru the exportRow array, but later on, you're not using the k iterator inside the for.
Then, you are not accessing the exportRows array, only the first position:
var URL = exportRows[0].toString().replace(re, replacingItem);
Shouldn't be?
var URL = exportRows[k].toString().replace(re, replacingItem);
In that case, it won't work, because URL it's not an array, so by doing this you are only saving the last assignation produced on the for iterator on the URL, I believe you are trying to do the following:
for ( k=0; k < exportRows.length; k++) {
var re = '115-396/'
var replacingItem = '';
exportRows[k] = exportRows[k].toString().replace(re, replacingItem);
}
And you'll have exportRows as an array of the desired url's without the 115-396 extensions.
Now you can place this on the spreadsheet with setValue as you were doing, but setValue is for strings, integers, etc, and not for arrays. For arrays you have setValues()
GoogleSheetsPastePage.getRange(2,1,exportRows.length,exportRows[1].length).setValues(exportRows);
But, then, the range of exportRows should match the range of your getRange selection, which I'm not sure it's happening.
Just to clarify it, exportRows.length is the length of the array, and exportRows[1] is the length of the string/url stored on the position 1 of the array.
Hope this helps, the question is not really clear neither the intentions, provide more info if still not working.
How to know the size of the range you're getting?
var myrange = GoogleSheetsPastePage.getRange(2,1,exportRows.length,exportRows[1].length)
Logger.log(myrange.getNumRows());
Logger.log(myrange.getNumColumns());
You'll be able to know the range you have on getRange and make it match with the exportRows size.
Make sure to check the attached documentation, and in case you have more doubts please open a new question related to it.
Related
This works perfectly if none of the data-validated cells raise an error on the sheet being written to. However, once the script reaches a cell where the copied value doesn't follow the data validation rules, it returns an error and stops executing. How can I ignore this error, continue to write into the cell, and continue running the script?
function addNew() {
Week = "2022-01-03"
x = 4
y = 17
for (var i=0; i<21; i++)
{
var readRange = SpreadsheetApp.getActive().getRange(`Versionality!I${x}:K${y}`)
var readValues = readRange.getValues();
var writeRange = SpreadsheetApp.openById("------");
var writeValues = writeRange.getRange(`${Week}!H${x}:J${y}`)
writeValues.setValues(readValues);
x += 17
y += 17
}
}
Here is an example of how you can “disable/enable” data validation to copy data:
Sample code:
function myFunction(){
// IMPORTANT: Make sure source and target dimensions are the same.
var sourceRange = SpreadsheetApp.getActive().getRange("source!B11:D11"); //Get source range of cells to be copied
var sourceRangeDVs = sourceRange.getDataValidations(); // cache the data validations currently applied to the sourceRange
sourceRange.clearDataValidations(); //clear validations on the source range before getting source values
var sourceValues = rangeSource.getValues(); //getting source values
//Next, get target range where the data will be set.
//Note: if you have to open a new file and you are handling several ranges to be copied in a loop,
// you may want to cache the target file on a variable to avoid opening every iteration of the loop
var targetRange = SpreadsheetApp.getActive().getRange("target!E6:G6"); //Get target range of cells for the values to be set
targetRange.setValues(sourceValues); //Set values copied from source in the target range
sourceRange.setDataValidations(sourceRangeDVs); //Set Data Validations cached back to source range
}
Please note that the spreadsheet names and ranges set on the sample code above are merely to exemplify, please modify it accordingly.
Let me propose this. You are doing a lot of getValues()/setValues() which can cause a performance issue. What I like to do is use only one getValues() and then we can extract what we need from the full array and write our portions of it as needed.
Also you were repeatedly opening the same spreadsheet by id.
I didn't test it because your date structure would be too hard to set up. But I'm pretty confident it will work.
function addNew() {
let Week = "2022-01-03";
let x = 3; // array index is 1 less than row
let y = 17;
let spread = SpreadsheetApp.getActiveSpreadsheet();
let readSheet = spread.getSheetByName("Versionality");
let readValues = readSheet.getDataRange().getValues();
let writeSheet = SpreadsheetApp.openById("------").getSheetByName(Week);
for (let i=0; i<21; i++) {
let subArray = [];
let j=x;
while( j < y ) {
subArray.push(readValues[j].slice(7,10)); // columns I to K
j++;
}
writeSheet.getRange(x+1,8,14,3).setValues(subArray); // columns H to J
x += 17;
y += 17;
}
}
I am using p5 loadJSON to import data, the data gives an array with so many records and sub-arrays.
For example, data.records[i].form_values["94d5"] gives me a name.
I am currently using a for loop to go through data.records.length and give me an array of some key pieces of data relative to each record I want to see.
Problem:
I want to loop through the records and get the value of:
data.records[i].form_values["94d5"].choice_values["0"], but some records don't have form_values["94d5"], for example. I just want to skip empty sections (leave undefined like empty items) insted i get an error that choice_values is undefined.
My loop is:
function gotData(data){
var i = 0;
var statusFind = data.records[i].status;
for(i = 0; i < data.records.length; i++) {
var returned = [data.records[i].form_values["6f71"], data.records[i].form_values.b059, data.records[i].form_values["94d5"], data.records[i].form_values.b62d, data.records[i].form_values["3493"].choice_values["0"], data.records[i].status, data.records[i].form_values.af80];
for (j = 0; j < returned.length; returned++){
if (returned[j] == searchKey) {
var result = [returned[0], returned[1], returned[2], returned[3], returned[4], returned[5], returned[6]];
array.push(result);
write();
}
}
}
}
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 want to send an email when a cell in column B reaches 5. However, I want part of the email to have the individual's name from column A. Here's my code so far:
function ifstatement() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Total");
var value = sheet.getRange("B3:B241").getValue();
if(value === 5) {
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," name "are you alright?");
}
}
I'm trying to define the variable "name" so that it adds the person's name from column A. Could anyone please help me!
The ideal way to get values from different ranges in a sheet is to pull all the data from the sheet as a single 2D array (A table essentially), and work through that to determine what you want to do. This avoids unnecessary complexity, and also ensures that your execution time stays low since you don't need to call the sheet.getRange() service multiple times.
On String concatenation: your string concatenation would not work. You need + between the strings. Go from "Hi," name "are you alright?" to "Hi, "+ name +" are you alright?".
Here is an example solution for you:
Using this example data:
Note: You don't need to know how the columns bit works, just how to use it, think of it as a small service to make life easier if you ever decide to add, or rearrange the spreadsheet's columns.
/*
* Run this to check the sheets values
* This is more verbose to aid with understanding
*/
function checkSheet() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Total');
var range = sheet.getDataRange();
var values = range.getValues();
var columns = getColumnHeaders(values);
//Loop through all the rows in the sheet and check if the 'Number' column is 5
for(var i = 0; i < values.length; i++){
var number = values[i][columns['Number']]; //Get the number from the table of values, utalizing the columns object to get the index
if(number === 5){
var name = values[i][columns['Name']];
var email = values[i][columns['Email']];
Logger.log(name);
Logger.log(email);
//MailApp.sendEmail(email, "Update", "Hi, "+ name +" are you alright?");
}
}
}
/*
* Generates a columns object that lets you reference columns by name instead of index
* Can be used like column['headerText'] to get the index of a column
* Is just a dynamic convenience to avoid referencing columns by index
*/
function getColumnHeaders(array){
var columns = {};
for(var i = 0; i < array[0].length; i++){
columns[array[0][i]] = i;
}
return columns;
}
Condensed checkSheet():
//Condensed version
function checkSheet2() {
var values = SpreadsheetApp.getActive().getSheetByName('Total').getDataRange().getValues();
var columns = getColumnHeaders(values);
for(var i = 0; i < values.length; i++){
if(Number(values[i][columns['Number']]) === 5){
//MailApp.sendEmail(values[i][columns['Email']], "Update", "Hi, "+ values[i][columns['Name']] +" are you alright?");
}
}
}
At this line:
var value = sheet.getRange("B3:B241").getValue();
You're using the method getValue() and it only returns the value of the top-left cell of the range, and you need to to get the values of the whole column A an B, so first set the range to A3:B241 then use the method getValues() to get the values as a two-dimensional array. The line should look like this:
var values = sheet.getRange("A3:B241").getValues();
Once you have the Array you need to loop through the values and check if the element at the index 1 values[i][1] is equal to 5. The line should look like this:
for (var i = 0; i < values.length; i++) {
if(values[i][1] === 5){
// Block of code to be executed if the condition is true
}
}
Finally, the configuration of paramaters you're using for the sendEmail() method is: (recipient, subject, body) the body of the message needs to be a String, you need to concatenate the "Hi,", the name that is in the index 1 values[i][1] and "are you alright?", to achieve that you need to use the the concatenation operator (+), the line should look like this:
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," + values[i][0] + " are you alright?");
The complete code:
function ifstatement() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Total");
var values = sheet.getRange("A3:B241").getValues();
for (var i = 0; i < values.length; i++) {
if(values[i][1] === 5){
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," + values[i][0] + " are you alright?");
}
}
I've got this code that is sorting through the various items in after effects and returning all the compositions in the project, then I narrow it down based on the specific composition i'm looking for, in this case one ending with assemble. I get the name and that's great but what I really need is the index number to come along with the name, so that when I search for assemble I get a return of app.project.item(3), its index in the project window. Every time I try to get the number from the array all I seem to get is the total number of items which doesn't help.
Thanks.
function retrieveProjectItems(itemType){
var typeOptions = ["Composition", "Folder", "Footage"];
for(var t = 0; t<3; t++){
if(itemType == typeOptions[t]){
var proj, itemTotal, curItem, itemArray;
itemAry = [];
proj = app.project;
itemTotal = proj.numItems;
for(var i = 1; i <= itemTotal; i++){
curItem = proj.item(i);
//alert(curItem.name);
if(curItem.typeName == itemType){
itemAry[itemAry.length] = curItem.name;
}
}
return itemAry;
}
}
}
retrieveProjectItems("Composition");
//alert(comps); lists all COMPS in the Array
var comps = itemAry;
var compWithAssemble;
for(var i in comps){
if(comps[i].indexOf("assemble") > -1){ ///search for part of the name///////////////////////////////////
compWithAssemble = comps[i];
break;
}
}
// compWithAssemble has the string you are looking for.
alert(compWithAssemble);
//app.project.item(3).selected = true;
compWithAssemble.selected = true; //I'm looking to make this work...
I am assuming you want to programatically find the composition with a layer named "assemble"
This bit of code
if(comps[i].indexOf("assemble") > -1){ ///search for part of the name///////////////////////////////////
compWithAssemble = comps[i];
break;
}
does not give you the results you want because comps[i] is a object of CompItem, not an Array or a collection. You need to first retrieve the Layer Collection for each comp[i]. Then, when you have that LayerCollection, you can find the layer named "assemble" by using the .byName() method. If you don't get a returned layer, you'll receive null, otherwise, you'll receive a Layer Object.
It might look something like:
var comps = itemAry;
var compWithAssemble;
for (var i in comps){
if(comps[i].layers.byName("assemble") != null) {
compWithAssemble = comps[i];
break;
}
}