Excel Office Scripts - Replace "blank cells" with the text "null" - excel

I'm trying to replace blank cells in column B (column name= Status) with the text "null". I have two different approaches that get me halfway there, however I can't figure out how to fully solution this. Any help would be greatly appreciated
Here's the excel table showing column B and a few cells in that column with blank cells
Here's two different scripts I created trying to solution this
Script 1= I can identify the blank cells, but cannot figure out how to set the values for the cells.
function main(workbook: ExcelScript.Workbook) {
let source = workbook.getWorksheet("Source");
let sourceTable = workbook.getTable("Source");
const statusVisibleRange = source.getUsedRange().getColumn(0);
const statusVisibleRangeShifted = statusVisibleRange.getOffsetRange(1,1);
const StatusFilter = "Status";
const StatusFilterValues = '';
let stringValue = "null";
let blankCells =
statusVisibleRangeShifted.getUsedRange().getSpecialCells(ExcelScript.SpecialCellType.blanks);
console.log(blankCells.getAddress());
}
Script 2=Script works if there are blank cells in column B (column name = Status), however if there are not any blank cells...it's filtering to all values and updating the statuses of everything in this column to "null".
function main(workbook: ExcelScript.Workbook) {
let source = workbook.getWorksheet("Source");
let sourceTable = workbook.getTable("Source");
const statusVisibleRange = source.getUsedRange().getColumn(0);
const statusVisibleRangeShifted = statusVisibleRange.getOffsetRange(1,1);
const StatusFilter = "Status";
const StatusFilterValues = '';
let stringValue = "null";
const statusFilter = sourceTable.getColumnByName(StatusFilter);
statusFilter.getFilter().applyValuesFilter([StatusFilterValues]);
statusVisibleRangeShifted.getUsedRange().setValue(stringValue);
statusFilter.getFilter().clear();

This worked for me ...
function main(workbook: ExcelScript.Workbook)
{
let worksheet = workbook.getWorksheet("Source");
let table = worksheet.getTable("Source");
let statusColumn = table.getColumnByName("Status");
let statusColumnRange = statusColumn.getRangeBetweenHeaderAndTotal();
let emptyStatusCells = statusColumnRange.getSpecialCells(ExcelScript.SpecialCellType.blanks);
if (emptyStatusCells != undefined) {
let rangeAreas = emptyStatusCells.getAreas();
rangeAreas.forEach(range => {
let values = range.getValues();
values.forEach(cellValue => {
cellValue[0] = "null";
})
range.setValues(values);
})
}
// Add additional logic here once the blank cells are dealt with.
}

Related

Excel office script - Delete row (except first 2 columns) after row has been copied to a new tab

I have a script that will move a row to another table if a certain cell within that row is filled.
The script then deletes the entire row. I would like for this to continue but instead of deleting the entire row it would be great if it deleted the entire row excepting the first 2 columns.
The current script is below:
function main(workbook: ExcelScript.Workbook) {
// You can change these names to match the data in your workbook.
const TARGET_TABLE_NAME = 'TableNAdded';
const SOURCE_TABLE_NAME = 'TableN';
// Select what will be moved between tables.
const FILTER_COLUMN_INDEX = 27;
const FILTER_VALUE = 'Y';
// Get the Table objects.
let targetTable = workbook.getTable(TARGET_TABLE_NAME);
let sourceTable = workbook.getTable(SOURCE_TABLE_NAME);
// If either table is missing, report that information and stop the script.
if (!targetTable || !sourceTable) {
console.log(`Tables missing - Check to make sure both source (${TARGET_TABLE_NAME}) and target table (${SOURCE_TABLE_NAME}) are present before running the script. `);
return;
}
// Save the filter criteria currently on the source table.
const originalTableFilters = {};
// For each table column, collect the filter criteria on that column.
sourceTable.getColumns().forEach((column) => {
let originalColumnFilter = column.getFilter().getCriteria();
if (originalColumnFilter) {
originalTableFilters[column.getName()] = originalColumnFilter;
}
});
// Get all the data from the table.
const sourceRange = sourceTable.getRangeBetweenHeaderAndTotal();
const dataRows: (number | string | boolean)[][] = sourceTable.getRangeBetweenHeaderAndTotal().getValues();
// Create variables to hold the rows to be moved and their addresses.
let rowsToMoveValues: (number | string | boolean)[][] = [];
let rowAddressToRemove: string[] = [];
// Get the data values from the source table.
for (let i = 0; i < dataRows.length; i++) {
if (dataRows[i][FILTER_COLUMN_INDEX] === FILTER_VALUE) {
rowsToMoveValues.push(dataRows[i]);
// Get the intersection between table address and the entire row where we found the match. This provides the address of the range to remove.
let address = sourceRange.getIntersection(sourceRange.getCell(i, 0).getEntireRow()).getAddress();
rowAddressToRemove.push(address);
}
}
// If there are no data rows to process, end the script.
if (rowsToMoveValues.length < 1) {
console.log('No rows selected from the source table match the filter criteria.');
return;
}
console.log(`Adding ${rowsToMoveValues.length} rows to target table.`);
// Insert rows at the end of target table.
targetTable.addRows(-1, rowsToMoveValues)
// Remove the rows from the source table.
const sheet = sourceTable.getWorksheet();
// Remove all filters before removing rows.
sourceTable.getAutoFilter().clearCriteria();
// Important: Remove the rows starting at the bottom of the table.
// Otherwise, the lower rows change position before they are deleted.
console.log(`Removing ${rowAddressToRemove.length} rows from the source table.`);
rowAddressToRemove.reverse().forEach((address) => {
sheet.getRange(address).delete(ExcelScript.DeleteShiftDirection.up);
});
// Reapply the original filters.
Object.keys(originalTableFilters).forEach((columnName) => {
sourceTable.getColumnByName(columnName).getFilter().apply(originalTableFilters[columnName]);
});
}
I didn't even know where to start with this.

Delete rows after certain time

you see, I have this code, it works in the next way, after a form submit, the data goes to a sheet called Responses, that data is sent to another sheet in the same spreadsheet, this one is called Store, i'm using another coz as you know, once you delete for example the answer set on row 1 by the form, the next value is put on row two, but i need to read the first row, so in my code whenever the data is on responses, is sent to Store's first row, after that is compared in another sheet calleh Termo, then all the data that is filtered in thermo that exist on Store, is used to create a Slides presentation.
The problem is the next, i need to delete the row on Store after the slide is created, so when the next form is submited, the Store's first row is empty and can ''store'' the new value, my problem is that in some way, it deletes the data on Store first, and then when it creates the presentation slide, is empty, all this using on form submi trigger, but when i execute in the editor both separately, it works...
const PRESENTATION_ID = '1Xkbhk5UvEeCqu6NsBdRD41ysvxv4aW2-upKyVKYJboA'; //hacer variable para redireccionar al slides correcto
//Para generar historial
var SOURCE_SPREADSHEET_ID;
const TARGET_SPREADSHEET_ID = "1VELwR-qZMbUSsK3_VUmz8x2FRULSiOy0UvAMMHdhPE4";
const SOURCE_SHEET_NAME = "Responses";
const TARGET_SHEET_NAME = "HISTORIAL";
var Comparador;
//Fin de variables pala generar historial
function moveData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("Responses"); // Nombre de la hoja
Absoluto = "1";
Comparador = "1";
SOURCE_SPREADSHEET_ID = "1VELwR-qZMbUSsK3_VUmz8x2FRULSiOy0UvAMMHdhPE4"
//NAME_COL_INDEX = 6;
//NAME_COL_INDEX2 = 12;
var cell1 = s.getRange('AU1');
var value1 = cell1.getValue();
var value2 = Absoluto;
var cell2 = s.getRange('B1');
//VAriables para el segundo manejo
//fin
if (value1==value2){
var ss = SpreadsheetApp.getActive();
var dailySheet = ss.getSheetByName('Responses');
var appendSheet = ss.getSheetByName('Store');
var values = dailySheet.getRange("Responses!2:" + dailySheet.getLastRow()).getValues();
values = values.filter(e=>e[0]); //gets rid of blank rows. filters based on the first column (index 0).
appendSheet.getRange(appendSheet.getLastRow()+1,1,values.length,values[0].length).setValues(values);
Utilities.sleep(8000);
}
}
function generatePresentationData(){
//let sheet = SpreadsheetApp.getActiveSheet();
let sheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1VELwR-qZMbUSsK3_VUmz8x2FRULSiOy0UvAMMHdhPE4/edit?resourcekey&pli=1#gid=1218501416").getSheetByName("Termo");
let marketData = getDataFromSheet_(sheet);
let chartFromSheet = getChartsFromSheet_(sheet);
let slides = getSlides_();
let masterSlide = slides[1];
masterSlide.duplicate();
writeDataToMinutaSlide_(slides, marketData);
function getDataFromSheet_(sheet){
let dataRange = sheet.getDataRange();
let data = dataRange.getValues();
data.shift();
return data;
}
function getChartsFromSheet_(sheet){
let charts = sheet.getCharts();
let chart = charts[0];
return chart;
}
function getSlides_(){
let presentation = SlidesApp.openById(PRESENTATION_ID);
let slides = presentation.getSlides();
return slides;
}
function writeDataToMinutaSlide_(slides, marketData){
let slideMinuta = slides[1];
let moveSlide = slides[1];
moveSlide.move(3);
for (let index = 0; index < marketData.length;index++){
slideMinuta.replaceAllText(`{{Accion${index}}}`,marketData[index][56]);
slideMinuta.replaceAllText(`{{Respo${index}}}`,marketData[index][57]);
slideMinuta.replaceAllText(`{{Date${index}}}`,marketData[index][61]);
slideMinuta.replaceAllText(`{{Area}}`,marketData[index][4]);
slideMinuta.replaceAllText(`{{Estacion${index}}}`,marketData[index][74]);
slideMinuta.replaceAllText(`{{Fecha${index}}}`,marketData[index][62]);
slideMinuta.replaceAllText(`{{Responsable${index}}}`,marketData[index][2]);
slideMinuta.replaceAllText(`{{Problema${index}}}`,marketData[index][9]);
slideMinuta.replaceAllText(`{{Duracion${index}}}`,marketData[index][10]);
slideMinuta.replaceAllText(`{{A${index}}}`,marketData[index][39]);
slideMinuta.replaceAllText(`{{N${index}}}`,marketData[index][40]);
slideMinuta.replaceAllText(`{{M${index}}}`,marketData[index][41]);
slideMinuta.replaceAllText(`{{E${index}}}`,marketData[index][42]);
slideMinuta.replaceAllText(`{{L${index}}}`,marketData[index][43]);
slideMinuta.replaceAllText(`{{I${index}}}`,marketData[index][44]);
slideMinuta.replaceAllText(`{{É${index}}}`,marketData[index][45]);
slideMinuta.replaceAllText(`{{Image${index}}}`,marketData[index][19]);
slideMinuta.replaceAllText(`{{Causa${index}}}`,marketData[index][11]);
slideMinuta.replaceAllText(`{{Motivo${index}}}`,marketData[index][12]);
slideMinuta.replaceAllText(`{{Raiz${index}}}`,marketData[index][69]);
slideMinuta.replaceAllText(`{{Subcausa${index}}}`,marketData[index][67]);
slideMinuta.replaceAllText(`{{Pilar${index}}}`,marketData[index][68]);
}
}
}
var ss = SpreadsheetApp.getActive();
var dailySheet = ss.getSheetByName('Store');
var values = dailySheet.getRange("Store!1:" + dailySheet.getLastRow()).getValues();
values.reverse().forEach((r,i)=>{
if (r=='del'){
sheet.deleteRow(values.length-i+1);
}
});
var source = ss.getRange("Store!A1:AT");
source.clear();
Does anyone know a way to make the clear rows function after the generate presentationslide? This is the Slide I'm generating whitin the data on Store

Excel Office Scripts - Copy and paste range if filtered data is not empty

Summary: I have a multi-step script where I need an IF statement to handle a scenario when the range I am copying from could be empty. I need the script to move onto the next step even if it "skips" the immediate next step.
Scenario: A step in the middle of the script is to set several column filters and then copy the filtered results to another table. If the filtered results are blank/empty ,I want the script to skip the "copyfrom" step and move onto the next step which releases the previous filters that were set.
I attached a screenshot of the excel sample showing the filtered results are blank. The shown filters need to be released/cleared but the script is just stopping because the results are blank and it can't complete the immediate next step (copyfrom).
Script: I had help from another user on here with an IF statement in a different scenario, I tried to use that logic as my base for my current use case...but wasn't successful.
Here's the current script:
function main(workbook: ExcelScript.Workbook)
{
let target = workbook.getWorksheet('Target');
let source = workbook.getWorksheet('Source');
let targetTable = workbook.getTable('Target');
let sourceTable = workbook.getTable('Source');
const visibleRange = source.getUsedRange().getColumn(0);
let statusColumn = sourceTable.getColumnByName("Status");
let statusColumnRange = statusColumn.getRangeBetweenHeaderAndTotal();
//Identify last used row in Target sheet
const usedRange = target.getUsedRange();
console.log(usedRange.getAddress());
//Insert new row after last used row in Target sheet
const startCell = usedRange.getLastRow().getCell(0,0).getOffsetRange(1,0);
console.log(startCell.getAddress());
const targetRange = startCell.getResizedRange(0,0);
// Clear all filters on the table so that script filters can be applied
sourceTable.getAutoFilter().clearCriteria();
//Replace blanks with "null"
let emptyStatusCells = statusColumnRange.getSpecialCells(ExcelScript.SpecialCellType.blanks);
if (emptyStatusCells != undefined) {
let rangeAreas = emptyStatusCells.getAreas();
rangeAreas.forEach(range => {
let values = range.getValues();
values.forEach(cellValue => {
cellValue[0]= "null";
})
range.setValues(values);
})
//Clear Occurrence seq formula and re-apply
let sourceShiftedVisibleRangeFormula = visibleRange.getOffsetRange(1, 2);
let C2 = source.getRange('C2');
sourceShiftedVisibleRangeFormula.getUsedRange().clear();
C2.setFormula("=COUNTIF($A$2:A2,A2)");
//Filter Sources
const DuplicateFilter = 'Duplicate';
const ValueOfDuplicateFilter = 'Duplicate';
const OccurrenceFilter = 'Occurrence';
const ValueOfOccurrenceFilter = '1';
const IncludeInDupFilter = 'Include Dup Filter';
const ValueOfIncDupFilter = 'Yes';
//Find columns to filter
const duplicateFilter = sourceTable.getColumnByName(DuplicateFilter);
const occurrenceFilter = sourceTable.getColumnByName(OccurrenceFilter);
const includeDupFilter = sourceTable.getColumnByName(IncludeInDupFilter);
//Select values to filter by
duplicateFilter.getFilter().applyValuesFilter([ValueOfDuplicateFilter]);
occurrenceFilter.getFilter().applyValuesFilter([ValueOfOccurrenceFilter]);
includeDupFilter.getFilter().applyValuesFilter([ValueOfIncDupFilter]);
//Set source visible range to copy from
console.log(visibleRange.getAddress());
//const shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
const sourceShiftedVisibleRange= visibleRange.getOffsetRange(1,4)
console.log(sourceShiftedVisibleRange.getAddress());
let sh = workbook.getActiveWorksheet();
let visTble = sh.getTable('Source');
let rv = visTble.getRangeBetweenHeaderAndTotal().getVisibleView();
if (rv.getRowCount() > 0){
let shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
//Paste into Target table
targetRange.copyFrom(shiftedVisibleRange.getUsedRange(), ExcelScript.RangeCopyType.all, false,
false);
//Clear Occurrence filter to show all duplicate rows
occurrenceFilter.getFilter().clear();
//Set the Include Dup Filter string values to logged
let stringValue= "logged"
//Update include Dup Filter to logged for duplicate rows moved to target table during this
process
sourceShiftedVisibleRange.getUsedRange().setValue(stringValue);
//Clear all other filters setby script
sourceTable.getAutoFilter().clearCriteria();
}
}
This is the section of the script that I am struggling with, if empty it should skip the "Paste into Target table step" and move onto the next step called "Clear Occurrence filter to show all duplicate rows"
let sh = workbook.getActiveWorksheet();
let visTble = sh.getTable('Source');
let rv = visTble.getRangeBetweenHeaderAndTotal().getVisibleView();
if (rv.getRowCount() > 0){
let shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
//Paste into Target table
targetRange.copyFrom(shiftedVisibleRange.getUsedRange(),
ExcelScript.RangeCopyType.all, false,
false);
//Clear Occurrence filter to show all duplicate rows
occurrenceFilter.getFilter().clear();
I think you can do this with your source table using the getVisibleView() method. Once you used that method, you could get the row count. Code that did that would look something like this:
function main(workbook: ExcelScript.Workbook){
let sh: ExcelScript.Worksheet = workbook.getActiveWorksheet();
let tbl: ExcelScript.Table = sh.getTable("Table1");
let rv: ExcelScript.RangeView = tbl.getRangeBetweenHeaderAndTotal().getVisibleView()
if (rv.getRowCount() > 0){
//more code here
}
}

Office Script, update table from another, worked now failing

I used what I found Here and it worked great for a few months. My Workbook would always have the latest table update. Starting today it fails every time. Referencing Script 2 during the Flow-
Worksheet getRange: The request failed with status code of 504, error code UnknownError"
//resizes the range
let rang: ExcelScript.Range = SelectedSheet.getRange("A2").getResizedRange(valuesRowCount, valuesColumnCount)
Script 1 =
function main(workbook: ExcelScript.Workbook)
{
let selectedSheet = workbook.getActiveWorksheet();
let usedRange = selectedSheet.getUsedRange();
// Delete range B:D
selectedSheet.getRange("B:D").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range G:I
selectedSheet.getRange("G:I").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range L:L
selectedSheet.getRange("L:L").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range M:M
selectedSheet.getRange("M:M").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range N:S
selectedSheet.getRange("N:S").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range P:X
selectedSheet.getRange("P:X").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range R:AW
selectedSheet.getRange("R:AW").delete(ExcelScript.DeleteShiftDirection.left);
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = selectedSheet.getTable("Table1");
//get table's column count
let tblColumnCount: number = tbl.getColumns().length;
//set number of columns to keep
let columnsToKeep: number = 22;
//set the number of rows to remove
let rowsToRemove: number = 0;
//resize the table range
let tblRange: ExcelScript.Range =
tbl.getRangeBetweenHeaderAndTotal().getResizedRange(rowsToRemove,
columnsToKeep - tblColumnCount);
//get the table values
let tblRangeValues: string[][] = tblRange.getValues() as string[][];
//create a JSON string
let result: string = JSON.stringify(tblRangeValues);
//return JSON string
return result;
Script 2 =
function main(workbook: ExcelScript.Workbook, tableValues: string) {
let selectedSheet = workbook.getActiveWorksheet();
let SelectedSheet: ExcelScript.Worksheet = workbook.getWorksheet("Database")
let usedRange = selectedSheet.getUsedRange();
//parses the JSON string to create array
let tableValuesArray: string[][] = JSON.parse(tableValues);
//gets row count from the array
let valuesRowCount: number = tableValuesArray.length - 1
//gets column count from the array
let valuesColumnCount: number = tableValuesArray[0].length - 1
//resizes the range
let rang: ExcelScript.Range = SelectedSheet.getRange("A2").getResizedRange(valuesRowCount, valuesColumnCount)
//sets the value of the resized range to the array
rang.setValues(tableValuesArray)
// Fit the width of all the columns in the Table.
SelectedSheet.getUsedRange().getFormat().autofitColumns();
selectedSheet.getUsedRange().getFormat().setHorizontalAlignment(ExcelScript.HorizontalAlignment.left);
}
Any idea what I am doing wrong?
Are you running Script 1 as part of a Flow too? If so, you should change this line in Script 1:
let selectedSheet = workbook.getActiveWorksheet();
When you run a script as part of a Flow, you need to specify the worksheet by ID or name. So, for example, you would change the line to something like:
let selectedSheet = workbook.getWorksheet("Sheet1")
where Sheet1 is the sheet you want to add the table to.
You have two different sheet variables in Script 2 as well. One is 'selectedSheet' and the other is "SelectedSheet'. You reference both later in the script. Do you mean to have both? And again, for scripts running as part of a Flow, always specify the sheet by ID or name that you are trying to reference. Let me know if that fixes things!
Have you tried rerunning your flow? It's possible there could have been some temporary issue with PowerAutomate. So perhaps the service was down.
The only potential issue I see is this
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = selectedSheet.getTable("Table1");
The newTable's name is not guaranteed to be Table1. So if it wasn't, your flow might not work correctly. You could get around that by setting tbl to the newTable. So you could either write code like this:
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = newTable;
Or just update tbl to code like this:
let tbl: ExcelScript.Table = selectedSheet.addTable(usedRange, true);

programmatically print out cell and comments contents from spreadsheet

I have a spreadsheet with worksheets (originally in Google Sheets, but I can export to .xlsx or .ods) and I would like to programmatically print out the cell values of the first line along with the comments in each cell. E.g. see screenshot below of worksheet (https://docs.google.com/spreadsheets/d/1DGsrEKrxfQm8sRzfLyqu4z6Hx8eDdkVDiYlN3Rwve6A/edit?usp=sharing):
There are 3 cells in row1 each with a comment in them.
I would like to programmatically print out the contents of this worksheet so that they look something like:
Cell:"field1",Comment:"key=foobar"
Cell:"field2",Comment:"key=bar"
Cell:"field3",Comment:"key=foobar"
Any ideas?
google-spreadsheets
function getNotes_(fileId, sheetName, rangeA1)
{
var data = [];
var file = SpreadsheetApp.openById(fileId);
var sheet = file.getSheetByName(sheetName);
var range = sheet.getRange(rangeA1);
var values = range.getValues();
var notes = range.getNotes();
var getResult_ = function(value, i) { data.push( 'Cell:"' + value + '",Comment:"' + rowNotes[i] +'"') };
var rowNotes = [];
for (var i = 0, l = values.length; i < l; i++)
{
rowNotes = notes[i];
values[i].forEach(getResult_);
}
return data;
}

Resources