I am a non-programmer and quite new to Office Scripts and I would love some help.
I am currently trying to delete entire rows if the cell in the "Change Flag" column, which happens to be the second column on the Excel sheet, contains the word "Delete" or "Deleted".
I managed to delete rows if they contain the word "Delete" with the following script but could not make the script cells with "Deleted" too. I tried throwing brackets in there but it unfortunately did not work.
Can someone suggest a best practice to handle the deletion of rows based on multiple text matches (delete if x = y OR z)?
function main(workbook: ExcelScript.Workbook) {
// Get the used range on the current sheet.
const currentSheet = workbook.getActiveWorksheet();
let table = workbook.getTables()[0];
// Get the RangeAreas object for each cell with a formula.
const usedRange = currentSheet.getUsedRange();
//get the values of the range
let values = usedRange.getValues();
//get the row count of the range
let rowCount = usedRange.getRowCount();
//starting at the last row, check if the cell in column '1' equals to 'Delete'. If it is, then delete the entire row.
for (let i = rowCount - 1; i >= 0; i--) {
if (values[i][1] == "Delete") {
usedRange.getCell(i, 1).getEntireRow().delete(ExcelScript.DeleteShiftDirection.up)
}
}
}
Your if statement should look like the below:
if (values[i][1] == "Delete" || values[i][1] == "Deleted")
If you are looking for more general tutorials, here is a page I found that explains if statements a little more: https://www.w3schools.com/jsref/jsref_if.asp
You can use the includes() method of the string object to do this. To use that method, you'd have to cast the value to a string using the toString() method like so:
if (values[i][1].toString().includes("Delete"))
Using includes, it will flag for both Delete and Deleted.
The words Delete and Deleted may be spelled in a variety of different ways (e.g. delete and deleted, DELETE and DELETED, etc.). Those different spellings will not be flagged by includes(). If want those different spellings to be evaluated in the same way, you can use a method like toLowercase() to do that. After calling that method, you'd just provide the includes() method with a lowercase version of the spelling like so:
if (values[i][1].toString().toLowerCase().includes("delete"))
This approach will also flag for any other text that includes delete. So if you have text in a cell like "this row should not be deleted" this code will flag that text as well.
Related
I was writing some code for automatically generating report and appending it into a Excel Web Spreadsheet. The program runs periodically, each run generates a set of data and inserts it into the first empty row in a specific spreadsheet file.
when writing code for inserting the row, I found that there is no API for searching where the first empty row is. Therefore I need to use the read API to search the row number for inserting new data.
The read API looks like this:
Since the spreadsheet is stored online, for performance issue, readCells is limited to return at most 30 rows of data each time.
Can I write a function to search the position of the first empty row in column A? Please note that the spreadsheet is for automatic reports, so the number of rows could be very large, and I want to generate the report quickly enough
In a comment you've said that we can assume that previously, data were always written contiguously starting from row 1. That being the case, you're looking for the handy getUsedRange function, probably with a true argument:
const range = worksheet.getUsedRange(true);
From the documentation:
The used range is the smallest range that encompasses any cells that have a value or formatting assigned to them. If the entire worksheet is blank, this function will return the top left cell (i.e. it will not throw an error).
Parameters
valuesOnly boolean
Optional. If true, considers only cells with values as used cells (ignoring formatting).
You'd get the used range, and then query the range to find out where it ends. The first blank row is the next row after the end of the range.
Basically I start from somewhere and get the next 30, and then check if any of the results is null. If not, I call the same function with another start that is 30 +previous start else I return start + index of null
async function readCell(column, start) {
const results = await readCells(`${column}${start}:${column}${start + 30}`);
const foundIndex = results.find(element => element === null);
if (foundIndex === -1) {
return await readCell(column, start + 30);
} else {
return foundIndex + start;
}
}
I want to import rows from one google sheet to the other, however source sheet imports a number of empty rows. Now I use a filter function to get rid of these rows but they will not disappear, can anyone tell me why?
var a = SpreadsheetApp.openByUrl("url").getSheetByName("Admin Use Only").getRange(4,1,6,21).getValues();
var b = SpreadsheetApp.getActive().getSheetByName('Credit_Detail');
b.getRange(b.getLastRow() +1, 1, a.length,21).setValues(a);
//filter function below:
var otarget=b.getRange(2,1,b.getLastRow()-1, 26).getValues();
var data=otarget.filter(function(r){
return !r.every(function(cell){
return cell === "";});
});
Logger.log(data);
b.getRange("A2:Z").clearContent();
b.getRange(3,1,data.length,data[0].length).setValues(data);
here's how I would do it. First, create an variable to store the array of the source. then run a for loop scanning the first column for empties. something like: for (var i = 0, i < data.length; i++) { if (data[i][0] != '') { XXXX } }
XXXX means that you can either put a code to create a new set of array which can be passed to the target sheet at once or use append row to transfer non blank rows to the target sheet one by one.
Note: Creating a new array to store non-empty rows would speedup the execution time if you are dealing with large data, thousands of rows.
I feel like I'm going about this in all the wrong way. I'm trying to automate some of my workload here. I'm cleaning up spreadsheets with 4 columns (A-E), 2000+ rows. Column B contains website URLs, column D contains the URL's business name, generated from another source.
Sometimes the tool doesn't grab the name correctly or the name is missing, so it populates the missing entries in column D with "------" (6 hyphens). I've been trying to make a function that takes an input cell, checks if the contents of the cell are "------", and if it is the function changes the contents of the input cell to the contents of the cell two columns to the left (which is generally a website url). This is what I've come up with.
function replaceMissing(input) {
var sheet = SpreadsheetApp.getActiveSheet();
//sets active range to the input cell
var cell = sheet.getRange('"' + input + '"');
//gets cell to fill input cell
var urlCell = sheet.getRange(cell.getRow(), cell.getColumn() - 2);
//gets contents of input cell as String
var data = cell.getValue();
//gets contents of urlCell as String
var data2 = cell.getValue();
//checks if input cell should be replaced
if (data === "------") {
//set current cell's value to the value of the cell 2 columns to the left
cell.setValue(data2);
}
}
When I attempt to use my function in my sheet, the cell is returning the error
Error Range not found (line 4).
I'm assuming, based on similar questions people have asked, that this is how you use the A1 notation of the function with an argument. However, that doesn't seem to be the case, so I'm stuck. I also don't think my solution is very good period.
1) It's somewhat ambiguous in GAS documentation, but custom functions have quite a few limitations. They are better suited for scenarios where you need to perform a simple calculation and return a string or a number type value to the cell. While custom functions can call some GAS services, this practice is strongly discouraged by Google.
If you check the docs for the list of supported services, you'll notice that they support only some 'get' methods for Spreadsheet service, but not 'set' methods https://developers.google.com/apps-script/guides/sheets/functions
That means you can't call cell.setValue() in the context of a custom function. It makes sense if you think about it - your spreadsheet can contain 1000s of rows, each with its own custom function making multiple calls to the server. In JavaScript, every function call creates its own execution context, so things could get ugly very quickly.
2) For better performance, use batch operations and don't alternate between read / write actions. Instead, read all the data you need for processing into variables and leave the spreadsheet alone. After processing your data, perform a single write action to update values in the target range. There's no need to go cell by cell when you can get the entire range using GAS.
Google Apps Script - best practices
https://developers.google.com/apps-script/guides/support/best-practices
Below is a quick code example that runs onOpen and onEdit. If you need more flexibility in terms of when to run the script, look into dynamically-created triggers https://developers.google.com/apps-script/reference/script/script-app
Because your spreadsheets have lots of rows, you may hit the execution quota anyway - by using triggers you can work around the limitation.
Finally, if a cell containing '----' is a rare occurrence, it might be better to create another array variable with new values and row numbers to update than updating the entire range.
Personally, I think the single range update action would still be quicker, but you could try both approaches and see which one works best.
function onOpen(){
test();
}
function onEdit() {
test();
}
function test() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('yourSheetName');
//range to replace values in
var range = sheet.getRange(2, 4, sheet.getLastRow() - 1, 1);
//range to get new values from
var lookupRange = range.offset(0, -2);
//2d array of values from the target range
var values = range.getValues();
//2d array of values from the source range
var lookupValues = lookupRange.getValues();
//looping through the values array and checking if array element meets our condition
for (var i=0; i < values.length; i++) {
values[i][0] = (values[i][0] == '------') ? lookupValues[i][0] : values[i][0];
}
// one method call to update the range
range.setValues(values);
}
What I'm looking for is an individual column searching function (exactly as this datatables spreadsheet example) for the Handsontable spreadsheet plugin.
What's already existing and has been developed by the Handsontable team is :
Multiple filtering Excel-like (but included in the PRO version) - Cons for my case are that it's not free, and it doesn't quite fit well what I'm looking for.
Highlighting the cell(s) or row(s) based on an user input - The Con is that I need to only display the relevant row(s)
Is there such thing as displaying only the relevant row(s) based on multiple inputs from an user with Handsontable ?
Based on the solution of this blog, I managed to code a solution.
See this JS fiddle that answers all my requirements.
The main function I was looking for is this one :
// The function push every row satisfying all the input values into an array that is loaded
function filter() {
var row, r_len, col, c_len;
var data = myData; // Keeping the integrity of the original data
var array = [];
var match = true;
for (row = 0, r_len = data.length; row < r_len; row++) {
for(col = 0, c_len = searchFields.length; col < c_len; col++) {
if(('' + data[row][col]).toLowerCase().indexOf(searchFields[col]) > -1);
else match=false;
}
if(match) array.push(data[row]);
match = true;
}
hot.loadData(array);
}
What I did is keeping synchronized a table of Strings with the input fields (searchFields), compare the data of each row between inputs and their corresponding column, and push into an array the relevant row(s) to finally display the resulting array. This function is called for any change in the input fields which result in a live table filtering.
Note that I tried my solution for ~10k rows and their isn't any performance issue with Chrome, Firefox and IE.
Also note that I managed to find a solution to keep synchronized the current displayed table with the original data when editing the values, but this is IMO out of the scope of this question. Please let me know in the comment if you're interested about this.
I currently have code that searches through a single Google sheet for indexOf a number, and assigns the row number of a match to an variable. Here's an example:
for(i=0;i<values.length; i++){
itemvalue = values[i][column1-1];
codevalue = values[i][column2-1];
if(itemvalue.indexOf("5019")>-1){
row = i+1; itemcode = 5019; indexes.push(i+1)
}
}
Unfortunately the sheet contains multiple rows of strings that contain "5019". So I'm looking for a way for indexOf to continue searching and assign a new variable for the row number of each match. So if "5019" was found at row 50,51, and 54, then row,row1,row2 will be assigned to each row number.
Appreciate any help on this, also please let me know if I missed providing any information.
Edit:Thanks to the link provided by daniel, I was able to get all the matching row numbers into an array. But I'm still trying to figure out how to assign a variable to each value in the array so I can get the value of the cells that reside at the rows and add them together.
I've found the solution I sought for. Here's what I did:
for(i=0;i<values.length; i++){
itemvalue = values[i][column1-1];
codevalue = values[i][column2-1];
if(itemvalue.indexOf("5019")>-1){
row = i+1; itemcode = 5019; indexes.push(i+1)
}
for(j=k=0;j<indexes.length; j++){
rownumb.push(ss.getRange(indexes[j],qtycol).getValue()); //Get value of the cells using the row numbers from the indexes array
}
for(l=0;l<rownumb.length; l++){
rowtotal += rownumb[l]; //Adds all the values together
}
}
So I made a for loop to get the value of cells in the rows that was in the indexes array, then made a new array called "rownumb" that holds the cell values. I then made another for loop to add all the values in array rownumb together and got exactly what I was looking for.