I'm creating a spreadsheet to track who did what on a day to day basis. For complicated reasons I don't want to go into the sheet has to be sorted by the rows rather than the columns.
My full script works great except it clears out the background colors of each cell when it transposes. I could transpose and sort by hand and keep the cell backgrounds (Certain cells have to be color-coded based on input from another sheet) but that's tedious and it's why people script in the first place.
I've tried editing the code below to getBackgrounds() and setBackgrounds() in various ways. I've come to the conclusion that I need help.
function Transpose() {
//This function Transposes it in order to sort since google doesn't let you sort by rows.
// get all the data in the sheet
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
var range = ss.getDataRange();
var values = range.getValues();
// clear existing
range.clear();
// transpose it & write it out
ss.getRange(1,1,values[0].length,values.length)
.setValues(Object.keys(values[0]).map ( function (columnNumber) {
return values.map( function (row) {
return row[columnNumber];
});
}));
}
Modifications
Access backgrounds somewhere along the lines:
var backgrounds = range.getBackgrounds();
Move transpose logic to a utility function and optimize it:
/**
* Transposes values;
* #param {Array<Array>} values input Array;
* #return {Array<Array>} transposed Array;
*/
function transpose(values) {
return values[0].map(function(col,c){
return values.map(function(row){
return row[c];
});
});
}
Combine previous steps in your main function, clean and simple:
function Transpose() {
// get all the data in the sheet
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
var range = ss.getDataRange();
//access values and backgrounds;
var values = range.getValues();
var backgrounds = range.getBackgrounds();
// clear existing;
range.clear();
//transpose;
values = transpose(values);
backgrounds = transpose(backgrounds);
//access target range - note that dimensions are inversed;
var target = ss.getRange(1,1,values.length,values[0].length);
//set values and backgrounds;
target.setValues(values);
target.setBackgrounds(backgrounds);
}
You could just use the inbuilt transpose:
function transpose() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
var range = ss.getDataRange();
range.copyTo(
ss.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
true
);
}
If you want to ignore formulas and just copy values/format,
["VALUES","FORMAT"].forEach(function(type){
range.copyTo(
ss.getRange('A1'),
SpreadsheetApp.CopyPasteType["PASTE_" + type ],
true
);
})
References:
Range#copyTo
Just apply backgrounds too
forEach as a consistent solution
/**
* Transposes with backgrounds
*/
function transpose() {
// get all the data in the sheet
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
var range = ss.getDataRange();
var values = range.getValues();
var backgrounds = range.getBackgrounds();
var transposeBackgrounds = [];
var transposeValues = [];
values[0].forEach(function(_, i) {
var rowValues = [];
var rowBackgrounds = [];
values.forEach(function(_, j) {
rowValues.push(values[j][i]);
rowBackgrounds.push(backgrounds[j][i]);
});
transposeValues.push(rowValues);
transposeBackgrounds.push(rowBackgrounds);
});
// clear existing
range.clear();
// transpose it & write it out
ss.getRange(1, 1, transposeValues.length, transposeValues[0].length)
.setValues(transposeValues)
.setBackgrounds(transposeBackgrounds);
}
The implementation of the current code
Based of that you can implement it on your code
function Transpose() {
// This function Transposes it in order to sort since bla-bla-bla
// get all the data in the sheet
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet3');
var range = ss.getDataRange();
var values = range.getValues();
var backgrounds = range.getBackgrounds();
var transposeBackgrounds = [];
// clear existing
range.clear();
// transpose it & write it out
ss.getRange(1, 1, values[0].length, values.length)
.setValues(
Object.keys(values[0]).map(function(columnNumber) {
var rowBackgrounds = [];
var newRow = values.map(function(row, i) {
rowBackgrounds.push(backgrounds[i][columnNumber]);
return row[columnNumber];
});
transposeBackgrounds.push(rowBackgrounds);
return newRow;
})
)
.setBackgrounds(transposeBackgrounds);
}
Related
I'm trying to set a dynamic dropdown in Google Sheet using Apps Script. I managed to get most parts working except setting the data validation in the cells necessary:
function sheetByName(ssId, sheetName) {
var ss = SpreadsheetApp.openById(ssId);
var sheet = ss.getSheetByName(sheetName);
return sheet;
};
function columnByName(sheet, columnName) {
var data = sheet.getDataRange().getValues();
var column = data[0].indexOf(columnName);
return column;
};
function columnValues(sheet, index) {
var data = sheet.getDataRange().getValues();
var values = [];
for(n=1; n<data.length; ++n) {
values.push(data[n][index]);
}
return values;
}
function columnSetDataValidation(sheet, index, options) {
var data = sheet.getDataRange().getValues();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
for(n=1; n<data.length; ++n) {
var cell = data[n][index];
};
};
function dropDownBedrijven() {
var sheetCollegas = sheetByName("<<ID HERE>>", "Collegas");
var sheetBedrijven = sheetByName("<<ID HERE>>", "Bedrijven");
var getColumnIndexInBedijven = columnByName(sheetBedrijven, "Bedrijf");
var getColumnIndexInCollegas = columnByName(sheetCollegas, "Bedrijf");
var bedrijven = columnValues(sheetBedrijven, getColumnIndexInBedijven).filter(item => item);
columnSetDataValidation(sheetCollegas, getColumnIndexInCollegas, bedrijven);
};
I can't manage to get the function columnSetDataValidation to set data validation in the required cells.
Do you have any idea how to go about it?
You need to use range.setDataValidation(rule) with a range.
In your function columnSetDataValidation you are correctly building the rule, but are failing to assign the rule to a range. You are looping over the values of the range and then changing the value of var cell until the loop ends. Nowhere did you call range.setDataValidation(rule).
Try the following solution:
function columnSetDataValidation(sheet, index, options) {
var range = sheet.getDataRange();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
for(n = 1; n < range.getLastRow(); ++n) {
var cell = range.getCell(n,index);
cell.setDataValidation(rule);
};
};
References:
Range.getCell(row, column)
Range.setDataValidation(rule)
Try this:
function columnSetDataValidation() {
const ss=SpreadsheetApp.getActive();
const sheet=ss.getSheetByName('Sheet1');
const range = sheet.getRange(2,4,sheet.getLastRow()-1);//putting validation in column 4
const options=[1,2,3,4,5];
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.setAllowInvalid(true)
.build();
range.setDataValidation(rule);
}
Is it possible to copy values after applying a filter?
I want to ignore the hidden values.
I need to filter a sheet with more than 2000 rows and if I use a loop it takes a long time.
Then, I use this:
var filteredRangefec = range.createFilter()
.setColumnFilterCriteria(6,filterCriteria)
.setColumnFilterCriteria(9, filterCriteriafecha)
.getRange();//range.getFilter().remove();
}
But when i use GetValues take all values, filter and not filter
If you want to retrieve the data and manipulate it in Google Apps Script, you could create temporary sheet, copy filtered data to temporary sheet using method:copyTo() with copyPasteType PASTE_NORMAL and use method:getDataRange() & method:getValues() to retrieve the data.
Example Data:
I copied TheMaster answer here and added some features:
function getFilteredValues(){
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var newSheet = activeSpreadsheet.getSheetByName("Temporary");
//check if existing, delete if yes
if (newSheet != null) {
activeSpreadsheet.deleteSheet(newSheet);
}
//create new sheet with name Temporary
newSheet = activeSpreadsheet.insertSheet();
newSheet.setName("Temporary");
var dataSheet = activeSpreadsheet.getSheetByName("Sheet1");
var toFilter = dataSheet.getDataRange();
var filter = toFilter.createFilter();
//create criteria
var criteria = SpreadsheetApp.newFilterCriteria();
criteria.whenNumberGreaterThan(1200);
//filter first column using the criteria above
filter.setColumnFilterCriteria(1, criteria.build());
//copy filtered data to temporary sheet
var sourceRange = dataSheet.getFilter().getRange();
sourceRange.copyTo(
newSheet.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
false);
Logger.log(newSheet.getDataRange().getValues());
activeSpreadsheet.deleteSheet(newSheet);
}
Output:
Note: You can also use this to paste the data to sheet.
This will do it. You just pass the range to the removeFilteredData() function and it will return the filtered array.
/**
* #param {SpreadsheetApp.Spreadsheet.Range} range
* #returns {Array<Array>}
*/
function removeFilteredValues(range) {
const values = range.getValues();
const firstRow = range.getRow();
const sheet = range.getSheet();
const filteredValues = values.filter((row, i) => {
return !(sheet.isRowHiddenByFilter(i + firstRow));
});
return filteredValues;
}
function main() {
const range = SpreadsheetApp
.getActiveSpreadsheet()
.getActiveSheet()
.getDataRange();
const result = removeFilteredValues(range);
}
The following is what I use from #Nikko idea above. You can copy the result to different spreadsheet.
function copyFilteredData(sourceID, sheetName, desID, desSheetName, filterCol, filterValue){
const sourceSS = SpreadsheetApp.openById(sourceID);
const sheet = sourceSS.getSheetByName(sheetName);
// remove filter if any
if (sheet.getFilter()) {
sheet.getFilter().remove();
}
var toFilter = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());
var filter = toFilter.createFilter();
// create criteria
var criteria = SpreadsheetApp.newFilterCriteria();
criteria.whenTextEqualTo(filterValue);
// filter first column using the criteria above
filter.setColumnFilterCriteria(filterCol, criteria.build());
// copy filtered data
const destinationSS = SpreadsheetApp.openById(desID);
const destSheet = destinationSS.getSheetByName(desSheetName);
var sourceRange = sheet.getFilter().getRange();
sourceRange.copyTo(
destSheet.getRange('A1'),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL,
false);
// clear our filter before leaving
if (sheet.getFilter()) {
sheet.getFilter().remove();
}
}
I am having issues converting my last script to work with getDataRange. I got some help converting my original functions, seen below:
function twentyDKP() {
alertBox20DKP()
}
function alertBox20DKP() {
var sh=SpreadsheetApp.getUi();
var response=sh.alert("Add 20 DKP to all raiders?", sh.ButtonSet.YES_NO);
if(response==sh.Button.YES) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var raiders = activeSheet.getRange(1, 12).getValue();
// In your situation, the range is the same. So "range" is declared here.
var range = activeSheet.getRange(4, 2, raiders);
// Create values for putting to the range.
var values = range.getValues().map(function(row) {return [row[0] + 20]});
// Put the created values to the range.
range.setValues(values);
// Update the cells. Before "alert" is shown.
SpreadsheetApp.flush();
var complete=sh.alert("20 DKP has been added to all raiders.", sh.ButtonSet.OK);
}
}
However, I now want to do the same with my subtraction script that would rely on two ranges of data. I'm still very new to coding, and am basically getting by on youtube tutorials and advice from this forum. How would I implement the same change to the below code?
function spentDKP() {
alertBoxSpentDKP()
}
function alertBoxSpentDKP() {
var sh=SpreadsheetApp.getUi();
var response=sh.alert("Subtract spent DKP of all raiders?", sh.ButtonSet.YES_NO);
if(response==sh.Button.YES) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var raiders = activeSheet.getRange(1, 12).getValue()+4;
for(var i=4;i<raiders;i++){
var DKP = activeSheet.getRange(i,2).getValue()
var spentDKP = activeSheet.getRange(i,4).getValue();
if(spentDKP>0){
activeSheet.getRange(i,2).setValue(DKP-spentDKP)
}
}
var complete=sh.alert("All DKP has been subtracted, please clear the loot window to reset values.", sh.ButtonSet.OK);
}
}
Many thanks in advance.
Try replacing this
var raiders = activeSheet.getRange(1, 12).getValue()+4;
for(var i=4;i<raiders;i++){
var DKP = activeSheet.getRange(i,2).getValue()
var spentDKP = activeSheet.getRange(i,4).getValue();
if(spentDKP>0){
activeSheet.getRange(i,2).setValue(DKP-spentDKP)
}
}
with this
var raiders = activeSheet.getRange(1, 12).getValue();
var range = activeSheet.getRange(4, 2, raiders, 3); // Get the range with the data to be processed
var inputValues = range.getValues(); // Get the data from that range
var outputValues = inputValues.map(Subtract); // Use a function to subtract the spent DKP from the DKP balance available
range.setValues(outputValues); // post the calculated values back to the sheet
And add a helper function for map:
// Helper function for map
function Subtract(inputValuesRow) {
if (inputValuesRow[2] > 0) {
inputValuesRow[0] -= inputValuesRow[2];
}
return inputValuesRow; // This has now been change where column 4 value has been subtracted from the column 2 value
}
Edit
To preserve the formulas in the middle column, remove the Subtract helper function. And use this as the replacement:
var raiders = activeSheet.getRange(1, 12).getValue();
var range = activeSheet.getRange(4, 2, raiders, 3); // Get the range with the data to be processed
var inputValues = range.getValues(); // Get the data from that range
var outputValues = [];
for (var r = 0; r < inputValues.length; r++) {
if ( inputValues[r][2] > 0 ) {
outputValues.push([inputValues[r][0] - inputValues[r][2]])
} else {
outputValues.push([inputValues[r][0]])
}
}
activeSheet.getRange(4, 2, raiders, 1).setValues(outputValues);
I have a script setup that moves all the records in a sheet into another sheet. However, I would like to add functionality to only move records that have the value "Approved" in a specific column (column I for my current situation). I've seen some other questions/answers to move individual rows based on values, but I haven't figured out how to leverage these scripts to run across a full spreadsheet. My guess is I can use a for loop but how?
Current script:
function MoveRecords(){
//Original Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Example Input")
//Calculate # of Rows Ignoring Blank Array Values
var Rows = ss.getRange("A2:A").getValues();
var NumRows = Rows.filter(String).length;
/*Add a For Loop here to only get the rows with Approved in Column I?*/
//Parsed Data to Move
var rangeValues1 = sheet.getRange(2,1,NumRows,1).getValue();
var rangeValues2 = sheet.getRange(2,2,NumRows,5).getValues();
//Destination of Parsed Data
var DestinationSS = SpreadsheetApp.openById('ID');
var DestinationSheet = DestinationSS.getSheetByName('Approved');
var DestinationLastRow = DestinationSheet.getLastRow()+1;
//Move the Data
DestinationSheet.getRange(DestinationLastRow,3,NumRows,1).setValue(rangeValues1);
DestinationSheet.getRange(DestinationLastRow,5,NumRows,5).setValues(rangeValues2);
};
Any help is greatly appreciated!
You can get all values, filter every row for Approved then write those rows into destination sheet.
function MoveRecords() {
//Original Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Example Input');
var values = sheet.getDataRange().getValues();
/*Add a For Loop here to only get the rows with Approved in Column I?*/
// col I = index 8
var res = values.filter(function(row) {
return row[8] == 'Approved';
});
//Parse Data to Move
var rangeValues1 = res.map(function(row) {
return [row[0]];
});
var rangeValues2 = res.map(function(row) {
row.shift();
while (row.length > 5) row.pop();
return row;
});
//Destination
var DestinationSS = SpreadsheetApp.openById('ID');
var DestinationSheet = DestinationSS.getSheetByName('Approved');
var DestinationLastRow = DestinationSheet.getLastRow() + 1;
//Move the Data
DestinationSheet.getRange(DestinationLastRow, 3, rangeValues1.length, 1).setValues(rangeValues1);
DestinationSheet.getRange(DestinationLastRow, 5, rangeValues2.length, 5).setValues(rangeValues2);
}
I am working on a google app script to fetch a range, multiply one column in the range by another column in the range, then output to a different column within the original range. This should be simple, but I can't figure it out.
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var data = sheet.getRange("A1:B22").getValues();
var newData = []
for (i in data){
var row = data[i];
var multiply = row[0] * row[1]
newData.push(multiply)
}
sheet.getRange(1,3,22,1).setValues(newData)
}
Thanks for the recommendation, but that didn't work unfortunately. The reason I'm not using the arrayformula function is because this is actually a small piece of a larger, more complicated function.
What did end up working is pushing the "multiply" variable into newData as an array by putting it in brackets.
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var data = sheet.getRange("A1:B22").getValues();
var newData = []
for (i in data){
var row = data[i];
var multiply = row[0] * row[1]
newData.push([multiply])
Logger.log(newData)
}
sheet.getRange(1,3,22,1).setValues(newData)
}