Count Bold Cells with ability to copy/drag in Google Sheets Scripts - google-apps-script

I have many rows that I want to count how many cells are bolded in a Google Sheets. I've looked through plugins and searched for pre-made code snippets (I have no programming skills).
I found a script that does exactly what I want it to do, but the cells it checks and where it outputs the answer is hard coded in located here (answer from user random-parts): Count Bold Cells in Google Sheets Script
I have several hundred rows that I want to know how many, if any, bolded cells, so I would have to make hundreds of separate scripts to use that. I've tried to turn it into a command I can drag/copy on the sheet but that skill is way beyond me.
This is the code that counts all the cells with bold, but the range input and output is hard coded in
var book = SpreadsheetApp.getActiveSpreadsheet();
var sheet = book.getActiveSheet();
var range_input = sheet.getRange("E2:S7");
var range_output = sheet.getRange("G14");
// Get the fontWeights of the range and flatten the array
var cell_styles = range_input.getFontWeights().join().split(",");
// Filter out any value that is not "bold"
var filter_bold = cell_styles.filter(function (e) { return e == "bold" });
// Set the count
range_output.setValue(filter_bold.length);
}
Can someone please provide a script and formula that would allow me to define a range in the sheet and then drag it and have it update?

Here is the link to a working example, and below is the code behind.
The important thing to note is that you need to refresh your browser window for formulas to re-calculate. The formula result cannot be refreshed by just changing the cells style.
function onOpen(){
refresh()
}
function refresh(){
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet2")
var formulaCol = sheet.getRange("E:E").getColumn()
var formulaRange = sheet.getRange(2, formulaCol, sheet.getDataRange().getLastRow()-1, 1)
var rowCount = formulaRange.getLastRow()-formulaRange.getRow()+1
var dummyFormulas = []
var formulas = formulaRange.getFormulas()
for(var i=0; i<rowCount; i++){
dummyFormulas.push(['=""'])
}
Logger.log(dummyFormulas)
Logger.log(formulas)
formulaRange.setFormulas(dummyFormulas)
SpreadsheetApp.flush()
formulaRange.setFormulas(formulas)
}
function countBoldCells(startRow, startColumn, endRow, endColumn, random){
var book = SpreadsheetApp.getActiveSpreadsheet();
var sheet = book.getActiveSheet();
var range_input = SpreadsheetApp.getActiveSheet().getRange(startRow, startColumn, endRow-startRow+1, endColumn-startColumn+1)
// Get the fontWeights of the range and flatten the array
var cell_styles = range_input.getFontWeights().join().split(",");
// Filter out any value that is not "bold"
var filter_bold = cell_styles.filter(function (e) { return e == "bold" });
return filter_bold.length;
}

Related

Apps script copy multiple range from 1 sheet to another spreadsheet

I am trying to copy data from 1 spreadsheet to another, I have successfully implemented something i found online that works with a specific range
function cloneGoogleSheet() {
// source doc
var sss = SpreadsheetApp.openById("spreadsheetkey1");
// source sheet
var ss = sss.getSheetByName('_tab_name_source');
// Get full range of data
var SRange = ss.getRange(7,3,5,1);
// get A1 notation identifying the range
var A1Range = SRange.getA1Notation();
// get the data values in range
var SData = SRange.getValues();
// target spreadsheet
var tss = SpreadsheetApp.openById("spreadsheetkey2");
// target sheet
var ts = tss.getSheetByName('tab_name_destination');
// Clear the Google Sheet before copy
//ts.clear({contentsOnly: true});
// set the target range to the values of the source data
ts.getRange(A1Range).setValues(SData);
};
The above piece coding work perfectly however I need to copy 18 different ranges that i cant just merge into 1 range. I considered the option of using the above however "multiplying" it 18 times for each range that however seems like a very inelegant solution.
I found a working solution that works if it stays within the same spreadsheet since it uses copyto instead of get/set values. The values part works perfectly since it doesnt mess with merge cells formatting. I have been struggling past 2-3 hours in merging the below-working code with elements from the first code to make a working script.
function test () {
try {
var spread = SpreadsheetApp.openById("spreadsheetkey");
var sheet = spread.getSheetByName("tab_name_source");
var rlist = sheet.getRangeList(["c7:c11", "g7:g11", "k7:k11"]);
sheet = spread.getSheetByName("tab_name_destination");
for( var i=0; i<rlist.getRanges().length; i++ ) {
var r1 = rlist.getRanges()[i];
var r2 = sheet.getRange(r1.getA1Notation());
r1.copyto(r2);
}
}
catch(err) {
Logger.log(err);
}
}
I tried initially to adapt the 2nd piece of coding to using setvalues however i had not been able to succesfully implement the part of getvalues within the scope of this code. I figured once I got this piece of code working with get and set values instead of Copyto i would only need to add the spreadsheetid of the other spreadsheet to get the final result
Try this:
function myFunction() {
var sourceSS = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = sourceSS.getSheetByName("sheetname");
var targetSS = SpreadsheetApp.openById("spreadsheet id here");
var targetSheet = targetSS.getSheetByName("Sheet1");
var ranges = ["C7:C11", "G7:G11", "K7:K11"];
ranges.forEach(range => {
var data = sourceSheet.getRange(range).getValues();
targetSheet.getRange(range).setValues(data);
})
}
Source sheet:
Destination sheet:
References:
setValues()
getValues()

Google Sheets filter values by value type

I am looking for help to create a filter on Google Sheets Script.
I want the following:
I have a database Schedule which has table with information that I want to filter
Once the database A is updated I want to filter Colum b row 8 and only take the cells that have information and are filled with words, numbers, etc.
After that I want to copy the data filtered and paste on a new Sheet Data “Foreman on specific columns
Also, I want to copy and paste the format such as color, size, etc.
I have the following code which it does the partial job but I cannot figure it out to only get the specific data needed
function Foreman(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var scheduleCCC_sheet = ss.getSheetByName("Schedule");
var Foreman_sheet = ss.getSheetByName("Foreman");
var pasteforemans = Foreman_sheet.getRange(8,2);
var originalData = scheduleCCC_sheet.getRange(9,2,scheduleCCC_sheet.getLastRow()1,11).getValues();
var filter1 ="";
Foreman_sheet.getRange(8,2,Foreman_sheet.getLastRow(),11).clearContent().clearFormat();
var data = originalData.filter(filterlogic);
Foreman_sheet.getRange(9,2,data.length,data[0].length).setValues(data);
}
var filterlogic = function(item){
if(item[1] == ""){
return false;
}else {
return true;
}
In order to filter the values based on the condition that the cells are not empty, you can use the below snippet of code:
Snippet
function myFunction() {
var ss = SpreadsheetApp.openById("ID_OF_THE_SS").getSheetByName("Schedule CCC");
var otherss = SpreadsheetApp.openById("ID_OF_THE_SS").getSheetByName("Foreman");
var range = ss.getRange("START_ROW", "START_COL", ss.getLastRow(), ss.getLastColumn());
var filter = range.getFilter();
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenCellNotEmpty().build();
filter.setColumnFilterCriteria("COL_POSITION", filterCriteria);
for (var i = 1; i <= ss.getLastRow(); i++) {
if (ss.isRowHiddenByFilter(i) == false) {
ss.getRange("RANGE_FROM_THE_CCC_SHEET").copyTo(otherss.getRange("RANGE_FROM_THE_FOREMAN_SHEET"));
}
}
}
Explanation
The above code gathers the filter from the source sheet and sets it the whenCellNotEmpty criteria. Afterwards it loops through the data and checks if a specific row is hidden by the filter; if the result of this is false, then it copies the row with its format onto the destination sheet. The format of the row is preserved by using the copyTo method.
Note
You will need to adjust the ranges to match the ones in your sheet and might need to add another condition when looping through the data from the source sheet.
Reference
Apps Script Sheet Class - isRowHiddenByFilter(rowPosition);
Apps Script Filet Class - setColumnFilterCriteria(columnPosition, filterCriteria);
Apps Script Sheet Class - copyTo(spreadsheet).

Improving Apps Script flexibility by using a column of sheet data instead of hard-coded IDs

Background: My coworkers originally each had a worksheet within the same Google Sheets file that makes a lot of calculations (and was getting unusable). Now, everyone has their own (known) Google Sheets file. To run the same calculations, we need to consolidate all that data into a master sheet (image ref below). We tried =importrange(...), but it's too heavy and breaks often (i.e., Loading... and other unfilled cells).
I've written some code to do this import, but right now its only manual: manually repeating the code and manually add the sheet IDs and changing the destrange.getRange(Cell range) each time. We have 80+ analysts, and fairly high turnover rates, so this would take an absurd amount of time. I'm new to Sheets and Apps Script, and know how to make the script use a cell as reference for a valid range or a valid ID, but I need something that can move a cell down and reference the new info.
Example:
Sheet 1 has a column of everyone Sheet ID
Script Pseudocode
get first row's id(Row 1), get sheet tab, get range, copies to active sheet's corresponding row(Row 1).
gets second row's id(Row 2), get sheet tab, get range, copies to active sheet's corresponding row (Row 2)
etc.
My script understanding is way to low to know how to process this. I have no idea what to read and learn to make it work properly.
function getdata() {
var confirm = Browser.msgBox('Preparing to draw data','Draw the data like your french girls?', Browser.Buttons.YES_NO);
if(confirm == 'yes'){
// I eventually want this to draw the ID from Column A:A, not hard-coded
var sourcess = SpreadsheetApp.openById('1B9sA5J-Jx0kBLuzP5vZ3LZcSw4CN9sS6A_mSbR9b26g');
var sourcesheet = sourcess.getSheetByName('Data Draw'); // source sheet name
var sourcerange = sourcesheet.getRange('E4:DU4'); // range
var sourcevalues = sourcerange.getValues();
var ss = SpreadsheetApp.getActiveSpreadsheet(); //
var destsheet = ss.getSheetByName('Master Totals'); //
// This range needs to somehow move one down after each time it pastes a row in.
var destrange = destsheet.getRange('E4:DU4');
destrange.setValues(sourcevalues); // Data into destsheet
}
}
Any suggestions are greatly appreciated!
Thanks to tehhowch for pointing me in the right direction!
function getdata() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var destsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Totals');
var confirm = Browser.msgBox('Drawing Data','Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO);
if(confirm =='yes'){
var lr = ss.getLastRow();
for (var i = 4; i<=lr; i++) {
var currentID = ss.getRange(i, 1).getValue();
var sourcess = SpreadsheetApp.openByUrl(currentID);
var sourcesheet = sourcess.getSheetByName('Data Draw');
var sourcerange = sourcesheet.getRange('E4:DU4');
var sourcevalues = sourcerange.getValues();
var destrange = destsheet.getRange('E' +i+':'+ 'DU'+ i);
destrange.setValues(sourcevalues);
I just had to learn how to use a variable loop.
Edit: thanks also to Phil for making my question more presentable!
Now that you've figured out one way to do it, I'll offer an alternative that uses batch methods (i.e. is much more time- and resource-efficient):
function getData() {
var wb = SpreadsheetApp.getActive();
var ss = wb.getActiveSheet();
var dest = wb.getSheetByName('Master Totals');
if (!dest || "yes" !== Browser.msgBox('Drawing Data', 'Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO))
return;
// Batch-read the first column into an array of arrays of values.
var ssids = ss.getSheetValues(4, 1, ss.getLastRow() - 4, 1);
var output = [];
for (var row = 0; row < ssids.length; ++row) {
var targetID = ssids[row][0];
// Open the remote sheet (consider using try-catch
// and adding error handling).
var remote = SpreadsheetApp.openById(targetID);
var source = remote.getSheetByName("Data Draw");
var toImport = source.getRange("E4:DU4").getValues();
// Add this 2D array to the end of our 2D output.
output = [].concat(output, toImport);
}
// Write collected data, if any, anchored from E4.
if(output.length > 0 && output[0].length > 0)
dest.getRange(4, 5, output.length, output[0].length).setValues(output);
}
Each call to getRange and setValues adds measurable time to the execution time - i.e. on the order of hundreds of milliseconds. Minimizing use of the Google interface classes and sticking to JavaScript wherever possible will dramatically improve your scripts' responsiveness.

How to clear conditional formatting (not all formatting) in Google Apps script

I need a way to remove all conditional formatting by running a script (my client will be using this and he doesn't want to have to repeat the process of removing conditional formatting for each worksheet in each of a large number of spreadsheet files).
Is there any way to do this via Google Apps script? All I see is .clearFormat(), which unfortunately clears all formatting, of which a lot should not be deleted (eg, font color, bg color, font, font weight, font rotation, cell outlines)
How to do this in such a way that only one button needs to be pressed for each spreadsheet file?
Google Apps Scripts now supports removing conditional formatting using clearConditionalFormatRules
var sheet = SpreadsheetApp.getActiveSheet();
sheet.clearConditionalFormatRules();
https://developers.google.com/apps-script/reference/spreadsheet/sheet#clearconditionalformatrules
This is possible with Google Sheets API v4, which Apps Script can access via Advanced Sheets Service (note that it must be enabled before use, as the linked page instructs). Here is a script that deletes all conditional formatting rules in Sheet1 of the current spreadsheet (you'll want to loop over sheets, etc).
function clearSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var sheet = ss.getSheetByName("Sheet1");
var sheetId = sheet.getSheetId();
var format_req = {
"requests": [{
"deleteConditionalFormatRule": {
"index": 0,
"sheetId": sheetId
}
}]
};
var string_req = JSON.stringify(format_req);
while (true) {
try {
Sheets.Spreadsheets.batchUpdate(string_req, ssId);
}
catch(e) {
break;
}
}
}
Each conditional format rule has a 0-based "index". Deleting the rule with index 0 causes other indices to decrease by 1. So the loop continues deleting index = 0 rule until there isn't one, and an error is thrown (and caught, exiting the loop).
This is a weird way of sending requests: I'd much rather send one batch request like this:
var format_req = {
"requests": [
{
"deleteConditionalFormatRule": {
"index": 0,
"sheetId": sheetId
}
},
{
"deleteConditionalFormatRule": {
"index": 1,
"sheetId": sheetId
}
}]
};
but to do this, one must know how many conditional formatting rules are there (and I don't see how one would find out). If you ask for more rules to be deleted than exist in the sheet, the entire request fails and nothing is deleted.
Without advanced service
With plain Apps Script, the best one can do is this:
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns());
var backgrounds = range.getBackgrounds();
var fontColors = range.getFontColor();
var fontFamilies = range.getFontFamilies();
// ... other get methods from https://developers.google.com/apps-script/reference/spreadsheet/range
// tricky part: modify the backgrounds, replacing the colors used in conditional formatting by white
range.clearFormat();
range.setBackgrounds(backgrounds)
.setFontColors(fontColors)
.setFontFamilies(fontFamilies)
// .set other things
Here I am assuming that conditional formatting affects only cell backgrounds. If one is unable to filter the background colors (which requires knowing exactly what colors were used in conditional formatting rules), the effects of conditional formatting will become ordinary background color, which is very undesirable... it may be better to forego setting the background colors at all.
So after my comment to if....
but to do this, one must know how many conditional formatting rules are there (and I don't see how one would find out)
decided to extend his code:
function get_clear_Formatting() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
// 1 part - get conditional formatting data for all sheets in active spreadsheet
var params = {
'fields': "sheets(properties(title,sheetId),conditionalFormats)"
};
var getFormatResult = Sheets.Spreadsheets.get(
ssId,
params
);
var sheets = getFormatResult.sheets;
var ConditionalFormatIndex = {
"sheetID" : [],
"sheetTitle" : [],
"formatRulesCount" : []
}
for (var i = 0; i < sheets.length; i++) {
ConditionalFormatIndex.sheetID[i] = sheets[i].properties.sheetId;
ConditionalFormatIndex.sheetTitle[i] = sheets[i].properties.title
ConditionalFormatIndex.formatRulesCount[i] = (sheets[i].conditionalFormats) ?
sheets[i].conditionalFormats.length :
0;
}
// 2 part - clear all conditional formatting in an all sheets in active spreadsheet
var ClearFormat_req = []
for (var i = 0; i < ConditionalFormatIndex.sheetID.length; i++) {
if ( ConditionalFormatIndex.formatRulesCount[i] ) {
for (var cf = 0; cf < ConditionalFormatIndex.formatRulesCount[i]; cf++) {
ClearFormat_req.push(
{
"deleteConditionalFormatRule": {
"index": 0,
"sheetId": ConditionalFormatIndex.sheetID[i]
}
});
}
};
}
Sheets.Spreadsheets.batchUpdate({'requests': ClearFormat_req}, ssId);
}
Copy a cell that doesn't have any conditional formatting.
Select the cell(s) you want to remove the conditional formatting from.
Edit -> Paste special -> Paste conditional formatting only.
A bit late, but I found a way, if that may help someone.
remove every conditional formatting on ranges intersecting the one in parameters
Then leave every other conditional formatting untouched (actually : rebuild it).
function test(){
var sh=shWork;//define your sheet
var r= sh.getRange("A3:A6");//example
clearEveryConditionalFormattingOnRange(sh,r)
}
function clearEveryConditionalFormattingOnRange(sh,r){
//build a parallel rules at looping on initial rule, in order to rebuild it without unwanted elements
//get rules
var rules=sh.getConditionalFormatRules();
//create new rules
var a_newRules= new Array();
//loop on rules
for (var i=0;i<rules.length;i++){
//create new currentRanges
var a_newCurrentRanges=new Array();
//loop on ranges from rule
var currentRule=rules[i];
var currentRanges=currentRule.getRanges();
for (var j=0;j<currentRanges.length;j++){
var currentRange=currentRanges[j];
var testIfIntersect_OK=RangeIntersect(r,currentRange);
//add this range to a_newCurrentRanges
if (!testIfIntersect_OK){
a_newCurrentRanges.push(currentRange);
}//if (testIfIntersect_OK){
}//for (var j=0;j<currentRanges.length;j++){
//create then add new rule to a_newRules
if (a_newCurrentRanges.length>0){
var a_newRule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied(currentRule.getBooleanCondition().getCriteriaValues())
.setBackground(currentRule.getBooleanCondition().getBackground())
.setRanges(a_newCurrentRanges)
.build();
a_newRules.push(a_newRule);
}//if (a_newCurrentRanges.length>0){
}//for (var i=0;i<rules.lengthi++){
sh.setConditionalFormatRules(a_newRules);
}
//returns true if intersection between range1 and range2
function RangeIntersect(R1, R2) {
var LR1 = R1.getLastRow();
var Ro2 = R2.getRow();
if (LR1 < Ro2) return false;
var LR2 = R2.getLastRow();
var Ro1 = R1.getRow();
if (LR2 < Ro1) return false;
var LC1 = R1.getLastColumn();
var C2 = R2.getColumn();
if (LC1 < C2) return false;
var LC2 = R2.getLastColumn();
var C1 = R1.getColumn();
if (LC2 < C1) return false;
return true;
}
I ended up finding a solution based on David Friedman's answer. This script successfully removed the conditional format from just one column (D), and left the conditional formats in other columns unchanged.
// clearConditionalFormat
// Data must have header row that does NOT have conditional formatting
// Otherwise you must identify some other cell on the sheet that does not
// have conditional formatting
function test(){
var sheetName = "Sheet13"; // replace with your sheet's name
var rangeText = "D3:D"; // replace with the your range
clearConditionalFormat(rangeText,sheetName);
};
function clearConditionalFormat(rangeText,sheetName){
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var rangeText = rangeText.toString().toUpperCase();
var range = ss.getRange(rangeText).activate();
var rangeTextSplit = rangeText.split(":");
// example: returns AA22 from AA22:AZ37
var rangeFirstLetter = rangeTextSplit[0].replace(/[0-9]+/g, "");
// example: returns AA from AA22
var rangeRowNum1 = rangeTextSplit[0].replace(/[A-Z]+/g, "")*1;
// example: returns the 22 of AA22:AZ37
var rangeHeaderText = rangeFirstLetter + (rangeRowNum1 - 1);
sheet.getRange(rangeHeaderText)
.copyTo(range,SpreadsheetApp.CopyPasteType
.PASTE_CONDITIONAL_FORMATTING, false);
};

How could you retrieve a range of user highlighted cells in Google Sheets using Google Apps Script?

At the moment this is the function I'm using but I have no way of testing if it will work in the spreadsheet until I publish the application.
function readSelection() {
//The commented lines aren't needed if the sheet is open already
//var sheetid = "sheet id here";
//var spreadsheet = SpreadsheetApp.openById(sheetid);
//SpreadsheetApp.setActiveSpreadsheet(spreadsheet);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
//sheet.setActiveSelection("B2:B22");
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
return activecells;
};
I assume you mean highlighted == selected
The result depends on whether the cells are contiguous or not (non contiguous cell selection is available in the new spreadsheets features http://googleblog.blogspot.co.nz/2013/12/new-google-sheets-faster-more-powerful.html
For contiguous cells selected your code returns the values of the selection as an array, for non-contiguous cells your code will return the an array with the single value of the LAST selected item.
I suggest that this is a bug in the implementation of the new spreadsheet. If it is important to you, I suggest you raise an issue. For the old spreadsheets, you can only select contiguous cells (eg B2:B22) so it will work as you expect.
The easiest way to answer this Q is to run the code you have written! You don't have to publish anything just run the code in the script editor of the spreadsheet you are examining
and look at the log.
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
Logger.log(activecells)
return
};
There is no way to do this at the moment or to obtain the selected ranges from a script.
A request is pending and you can support it here : https://code.google.com/p/google-apps-script-issues/issues/detail?id=4056
by adding a star to the request.
If/when this function is implemented your code would look as follows:
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activeranges = sheet.getSelectedRanges();
var activecells = [] ;'
for (var ar in activeranges)
activecells = activecells.concat(activeranges[ar].getValues()) ;
Logger.log(activecells)
return ;
}
note that selected ranges may overlap, so some cell contents could be added twice.