Google Script: Clear content of cells with specific background (to slow) - javascript

I am new to Google Script and i got stuck.
The code below works perfectly, but it takes more than 10 min to loop through all named ranges.
It clears the content of cells, but not the one with green backgrounds.
function deletNoGreenCells() {
var namedRange = ['HaM','BeM','LoM']
for (var k=0; k<namedRange.length; ++k) {
var range=SpreadsheetApp.getActiveSpreadsheet().getRangeByName(namedRange[k])
for( var i = 1; i < range.getNumRows()+1; ++i){
for(var j = 1; j<range.getNumColumns()+1;++j){
if (range.getCell(i, j).getBackground()!= "#93c47d") {
range.getCell(i, j).clearContent()
}}}}}
How can i get this faster?
Cheers!

Thank You Matthew for the link to call getBackgrounds, wich leeds me to this solution:
function deletNoGreenCells() {
var namedRange = ['HaM','BeM','LoM']
for (var k = 0; k < namedRange.length; ++k) {
var range =SpreadsheetApp.getActiveSpreadsheet().getRangeByName(namedRange[k])
var backgrounds = range.getBackgrounds();
for(var i = 0; i < range.getNumRows(); ++i) {
for(var j = 0; j<range.getNumColumns(); ++j) {
if (backgrounds[i][j] != "#93c47d") {
range.getCell(i+1, j+1).clearContent()
}
}
}
}
}
Now it runs only 5 seconds. Thanks!

I haven't tried this, but if you're just clearing the data from the non-green cells you might be able to do this:
Call getData on the range to get an array containing all the values
Call getBackgrounds on the range to get the corresponding array of background colours
Use the backgrounds array to update the data array, blanking out the elements you want to clear
Call setData on the range, passing back the modified array
It seems likely that reading and writing the data in big blocks like this will be quicker.

Related

Define multiple ranges in Google Apps Script

I'm having difficulty defining multiple ranges in GAS.
I have the following simple function I need to perform:
var dataRange = sheet.getRange(checkRange);
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == false) {
values[i][j] = true;
}
}
}
dataRange.setValues(values);
My range is actually defined by another function:
var checkRange = [];
for(var i = 0; i<checkboxes.length; i++) {
if(checkboxes[i]) {
checkRange.push('C' + (i+9));
Logger.log(checkRange);
}
}
Now, the required range is being created nicely as I can see in my logs. However, clearly the format that GAS required for cell ranges is different as my range is not defined. Furthermore, I have tried to work out the precise acceptable way of writing a range in GAS. If I put a range like 'C9:C11' the script works fine. If I put a list like 'C9, C10' or 'C9', 'C10' etc. it does not. Neither do multiple ranges ('C9:C11', 'C13:C14') etc... not quite sure how I need to write this
Performing the same operation on possibly-disjoint Ranges is most easily done with the Rangelist class, and does not require directly accessing the member Ranges. However, even if the operations are different (or conditional), a RangeList can be used to optimize the use of the Spreadsheet Service, rather than repeatedly calling Sheet#getRange.
From your code, we can determine that the goal is to operate on a set of ranges related to "true" checkboxes:
var checkRange = [];
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i]) {
checkRange.push('C' + (i+9));
Logger.log(checkRange);
}
}
You then (appear to) have a conditional alteration of the related range's value:
var dataRange = sheet.getRange(checkRange);
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == false) {
values[i][j] = true;
}
}
}
dataRange.setValues(values);
Instantiating a RangeList is done with an Array of string references to ranges on a sheet, e.g. [ "A1:A10", "C2", "D6:E8", "R5C9:R100C9" ]. Thus, it appears your current checkRange has the desired format already.
Your consumption code is then something like the following:
const rl = sheet.getRangeList(checkRange);
rl.getRanges().forEach(function (rg) {
// Assumption: only single-cell ranges, based on the above checkRange code
if (rg.getValue() == false) // matches false, null, undefined, 0, or ""
rg.setValue(true);
});
When accessing ranges from Google Apps Script each Range must be a continuous block, you can't select discrete ranges like C9:C11 and C13:C14 as a single Range object. You need to access those ranges separately.
A simple, but potentially inefficient, way to modify your existing code would be to loop over your checkRange array and access them one at a time:
for(var j = 0; j < checkRange.length; j++){
dataRange = sheet.getRange(checkRange[j]);
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == false) {
values[i][j] = true;
}
}
}
}
If each range is just a single cell, you can switch getValues() for getValue(), and then you don't need to loop over your values array.
However, this will be very inefficient if you are accessing what could be one large range as a series of smaller ranges. It would be better to either use some logic to build multi-cell ranges where possible, or to read in the entire sheet as a single range (sheet.getDataRange().getValues()), and then use Array notation to access the cells you are interested in, before writing back the entire array.

How to hold all the data bound, not just the data of the visible page

First, I'm sorry I do not have enough English Level.
My Grid shows 20 rows on a page. To use Excel export with client template, I used the following source found in the forum.
function excelExportWithTemplates(e) {
var sheet = e.workbook.sheets[0];
var colTemplates = [];
var data = this.dataSource.view();
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].template) {
colTemplates.push(kendo.template(this.columns[i].template));
} else {
colTemplates.push(null);
}
}
for (var i = 0; i < colTemplates.length; i++) {
for (var j = 0; j < data.length; j++) {
if (colTemplates[i] != null) {
sheet.rows[j + 1].cells[i].value = colTemplates[i](data[j]);
}
}
}
}
For example, if I have a total of 100 data, only 20 data, the size of one view,
The remainder can not be applied.
it doesn't mean
ExcelExport don't work well, I mean ExcelExport with ClientTemplate do work just only 20rows. (my view page amount)
To do this, add data.Source.View
I tried changing it to total
Total is just counting the number,
No conversion has been made.
To convert all data
What should I turn .view into?
The view() method will return only the rendered data on the viewport. Use the data() method instead, which will return all dataSource's data:
var data = this.dataSource.data();

Returning XML node values

I need to return all XML values from a URL.
I have previously used the URL and it works fine.
This is what I have so far:
function displayXML(xml) {
var devices = xml.getElementsByTagName("device");
for (var i = 0; i < devices.length; i++) {
var deviceDetails = devices[i].children;
for (j = 0; j < deviceDetails.length; j++) {
console.log(devices[i].childNodes[j].nodeValue);
}
}
}
It manages to return the right amount of values: 33 tags 33 values
but it's returning null for each one. However, the XML file contains values for each tag.
Thanks
Based on an answer to this question
The nodeValue property of XML elements is always null. The value of the element is actually stored within text nodes inside the element so you will need to go down one more child to get it. Try this
var devices = xml.getElementsByTagName("device")[i].firstChild.nodeValue;
I think your script should look something like this with firstChild inserted when trying to get the value:
function displayXML(xml) {
var devices = xml.getElementsByTagName("device");
for (var i = 0; i < devices.length; i++) {
var deviceDetails = devices[i].children;
for (j = 0; j < deviceDetails.length; j++) {
console.log(devices[i].childNodes[j].firstChild.nodeValue);
}
}
}

Google Sheet Script - Return header value if cell value found

Thank you in advance for your help.
I have a google sheet that contains header values in the first row. I have a script that is looking through the remainder of the sheet (row by row) and if a cell is a certain color the script keeps a count. At the end, if the count number is greater than a variable I set in the sheet the script will trigger an email.
What I am looking at trying to do, is to also capture the column header value if the script finds a cell with the set color? I'm sure I need to create an array with the header values and then compare the positions, I'm just not sure how to do so efficiently.
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var lastRow = dataSheet.getLastRow();
var lastColumn = dataSheet.getLastColumn();
//Project Range Information
var projectRange = dataSheet.getRange(6,3,lastRow-5,lastColumn);
var projectRangeValues = projectRange.getValues()[0];
var cellColors = projectRange.getBackgrounds();
//Student Information Range
var studentRange = dataSheet.getRange(6,1,lastRow-5,lastColumn);
var studentRangeValues = studentRange.getValues();
//Pull email template information
var emailSubject = ss.getRange("Variables!B1").getValue();
var emailText = ss.getRange("Variables!B2").getValue();
var triggerValue = ss.getRange("Variables!B4").getValue();
var ccValue = ss.getRange("Variables!B5").getValue();
//Where to Start and What to Check
var colorY = ss.getRange("Variables!B6").getValue();
var count = 0;
var startRow = 6;
//Loop through sheet and pull data
for(var i = 0; i < cellColors.length; i++) {
//Pull some information from the rows to use in email
var studentName = studentRangeValues[i][0];
var studentBlogUrl = studentRangeValues[i][1];
var studentEmail = studentRangeValues[i][2];
var studentData = [studentName,studentBlogUrl];
//Loop through cell colors and count them
for(var j = 0; j < cellColors[0].length ; j++) {
if(cellColors[i][j] == colorY) {
/*This is where I feel I need to add the array comparisons to get the header values */
count = count + 1;
};//end if statement
};//end for each cell in a row
//If the count is greater than trigger, send emails
if (count >= triggerValue) {
//A call to another function that merges the information
var emailBody = fillInTemplateFromObject(emailText, studentData);
MailApp.sendEmail({
to: studentEmail,
cc: ccValue,
subject: emailSubject,
htmlBody: emailBody,
});
} else {};
//reset count to 0 before next row
count = 0;
};//end for each row
};
EDIT:
I have updated the above sections of the code to based on the responses:
//Header Information
var headers = dataSheet.getRange(4,4,1,lastColumn);
var headerValues = headers.getValues();
var missingAssignments = new Array();
In the for loop I added:
//Loop through cell colors and count them
for(var j = 0; j < cellColors[0].length ; j++) {
if(cellColors[i][j] == colorY) {
//This pushes the correct information into the array that matches up with the columns with a color.
missingAssignments.push(headervalues[i][j]);
count = count + 1;
};//end if statement
};//end for each cell in a row
The issue I am running into is that I am getting an error - TypeError: Cannot read property "2" from undefined. This is being caused by the push in the for loop as the script moves to the next row. I am unsure why I am getting this error. From other things I have read, the array is set as undefined. I have tried to set the array to empty and set it's length to 0, but it does not help. I don't think I understand the scoping of the array as it runs through.
EDIT:
Figured it out, the "i" should not iterate. It should read:
missingAssignments.push(headervalues[0][j]);
The end of the first for loop I clear the array for the next row.
missingAssignments.length = 0;
You should get the values of the entire sheet. Then use the shift method to get just the headers. It is hard for me to completely understand your intent without more information about your sheet. Let me know if I can provide more information.
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var lastRow = dataSheet.getLastRow();
var lastColumn = dataSheet.getLastColumn();
//below gets the whole sheet and shifts off the first row as headers
var fullSheet = dataSheet.getDataRange().getValues();
var headers = fullSheet.shift();
//then in your loops you can check against the index of the headers array
Spreadsheets with Apps Scripts is really slow especially if you have lots of data to read.
Check these tips from Apps doc:
Use batch operations
Scripts commonly need to read in data from a spreadsheet, perform
calculations, and then write out the results of the data to a
spreadsheet. Google Apps Script already has some built-in
optimization, such as using look-ahead caching to retrieve what a
script is likely to get and write caching to save what is likely to be
set.
You can write scripts to take maximum advantage of the built-in
caching, by minimizing the number of reads and writes. Alternating
read and write commands is slow. To speed up a script, read all data
into an array with one command, perform any operations on the data in
the array, and write the data out with one command.
Here's an example — an example you should not follow or use. The
Spreadsheet Fractal Art script in the Gallery (only available in the
older version of Google Sheets) uses the following code to set the
background colors of every cell in a 100 x 100 spreadsheet grid:
// DO NOT USE THIS CODE. It is an example of SLOW, INEFFICIENT code.
// FOR DEMONSTRATION ONLY
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
xcoord = xmin;
for (var x = 0; x < 100; x++) {
var c = getColor_(xcoord, ycoord);
cell.offset(y, x).setBackgroundColor(c);
xcoord += xincrement;
}
ycoord -= yincrement;
SpreadsheetApp.flush();
}
The script is inefficient: it loops through 100 rows and 100 columns,
writing consecutively to 10,000 cells. The Google Apps Script
write-back cache helps, because it forces a write-back using flush at
the end of every line. Because of the caching, there are only 100
calls to the Spreadsheet.
But the code can be made much more efficient by batching the calls.
Here's a rewrite in which the cell range is read into an array called
colors, the color assignment operation is performed on the data in the
array, and the values in the array are written out to the spreadsheet:
// OKAY TO USE THIS EXAMPLE or code based on it.
var cell = sheet.getRange('a1');
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
xcoord = xmin;
colors[y] = new Array(100);
for (var x = 0; x < 100; x++) {
colors[y][x] = getColor_(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
The
inefficient code takes about 70 seconds to run. The efficient code
runs in just 1 second!
If you're looking at the Spreadsheet Fractal Art script (only
available in the older version of Google Sheets), please be aware that
we made a minor change to it to make this example easier to follow.
The script as published uses the setBackgroundRGB call, rather than
setBackgroundColor, which you see above. The getColor_ function was
changed as follows:
if (iteration == max_iteration) {
return '#000000';
} else {
var c = 255 - (iteration * 5);
c = Math.min(255, Math.max(0, c));
var hex = Number(c).toString(16);
while (hex.length < 2)
hex = '0' + hex;
return ('#' + hex + '3280');
}

I'm having difficulty populating a two dimensional array with random Boolean values and then reading those values back out in JavaScript

I'm working on a really simple cellular automata program in JavaScript. For right now I just want to write a bunch of random Boolean values to a two-dimensional array and then read that array back to be manipulated or displayed in some way.
var dimension = 5;
var grid = new Array();
for (x = 0; x < dimension; x++) {
grid[x] = new Array();
}
//populate grid
for (i = 0; i < dimension; i++) {
document.write('<br>');
for (j = 0; j < dimension; j++) {
grid[i,j] = Math.floor(Math.random()*2);
document.write(grid[i,j]);
}
}
for (i = 0; i < dimension; i++) {
document.write('<br>');
for (j = 0; j < dimension; j++) {
document.write(grid[i,j]);
}
}
So, I've whittled the code down to a few nested for loops where I cycle through and populate the array and then print it back. Note that the output generated during the population loop is what I want, random values. But when I read the array back, it seems like the last row (I think it's really a column, but it's displayed horizontally) has been copied to all the others...
I've done this sort of thing before in other languages and never had a problem like this.
I'm new to this community and JavaScript in general so this might be a dumb questing or I may not have presented it helpfully. I would really appreciate any help or advice on how I can improve my question.
Array indexes in JavaScript are not comma seperated. You need to use brackets. So for your two dimensional array it will be:
grid[i][j]; // not grid[i,j]

Categories

Resources