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.
Related
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();
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);
}
}
}
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.
i wrote the following loop based on the code found here:
How do I iterate through table rows and cells in javascript?
function myRowLooper() {
var inputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('INPUT');
var inputRange = inputSheet.getRange(2,inputSheet.getLastColumn(),inputSheet.getLastRow());
for (var i = 0, row; row = inputRange.rows[i]; i++) {
Logger.log(row);
for (var j = 0, col; col = row.cells[j]; j++) {
Logger.log(col);
}
}
}
but when I apply it to Google scripts it throws an error: "TypeError: Cannot read property "0" from undefined."
What's causing this?
Because you can't get any value from 'inputRange.rows[i]'.
You may do something like this :
function myRowLooper() {
var inputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
for(var i = 1; i<=inputSheet.getLastRow(); i++){
for(var j = 1; j<=inputSheet.getLastColumn(); j++){
var cell = inputSheet.getRange(i,j).getValue();
Logger.log(cell);
}
}
}
Hope this will help you.
Thanks.
Your code is extremely sloppy. You are trying to combine variables and condense unnecessarily and it's leading to errors in your code. There's no use in added "efficiency" if it leads to errors and mistakes.
Try something like this --
function yourFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Name");
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getMaxColumns();
var conditionToCheckColumn = (lastColumn - 1);
var conditionRange = sheet.getRange(2, checkColumn, (lastRow - 1), 1);
var check = checkRange.getValues();
for (var i = 0; i < check.length; i++) {
if (check[i] == condition) {
continue;
} else {
//your code here
}
}
}
This will pull a range at which you can check it's value/ condition and if matches, it does nothing. If it does not match, it will perform code. It will then loop to the next row until the last row in your check range that has data.
Be warned - functions count as data despite the cell being visibly empty. If your sheet uses functions like =QUERY, you will have an infinitely looping code unless your =QUERY (or other fx()) has a specific upper limit.
I am trying to build a simple script to work with a Google Spreadsheet that looks like this:
It shows me which of my clients use which modules of each layer. Each client may use how many modules he wants to.
What I need to do is count how many clients have all the modules installed (or the three layers, it's the same).
I've tried to do this using the built-in functions but have not succeed.
Now I'm trying to do my own function, that looks like this:
function countTotalModByClient(values) {
var quantMod=0;
var quantClient=0;
for (var i=0; i<values.length; i++) {
for(var j=0; j<values.length; j++) {
if(values[i][j]=="X") {
quantMod++;
}
}
if(quantMod==15) { // total number of modules
quantClient++;
}
quantMod=0;
}
return quantClient;
}
But it always return the same result: 0.
At my sheet, I'm calling the function like this: =countTotalModByClient(B3:P6)
P.S.: Sorry about the magic number in the code, I´ll fix this. =)
This should be possible with a standard formula (although maybe a little complex).
=countif(ArrayFormula(MMULT(--(B3:P6="X"), transpose(column(B3:P2)^0))), 15)
should return the number of clients with a count of 15 in their row..
Can you check if that works ?
Or if you prefer a custom function, give this a try:
function countTotalModByClient(values) {
var quantClient = 0;
for (var i = 0; i < values.length; i++) {
if (values[i].countItem("X") === 15) quantClient += 1;
}
return quantClient;
}
Array.prototype.countItem = function (item) {
var counts = {};
for (var i = 0; i < this.length; i++) {
var num = this[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
return counts[item] || 0;
}
Example spreadsheet