Office Scripts - Autofit columns after table manipulations - office-scripts

I'm trying to get the following to work with the autosize...
function main(workbook: ExcelScript.Workbook) {
// If the worksheet already exists, delete it
if (workbook.getWorksheet("Wonderful Life")) {
console.log("Deleting the Wonderful Life worksheet.");
workbook.getWorksheet("Wonderful Life").delete();
}
// The getData call could be replaced by input from Power Automate or a fetch call.
const data = getData();
// Create a new worksheet and switch to it.
const newSheet = workbook.addWorksheet("Wonderful Life");
newSheet.activate();
newSheet.getFreezePanes().freezeRows(1);
newSheet.setTabColor("002060");
// Get a range matching the size of the data.
const dataRange = newSheet.getRangeByIndexes(
0,
0,
data.length,
data[0].length);
// Set the data as the values in the range.
dataRange.setValues(data);
// Create a table using the data range.
let newTable = workbook.addTable(dataRange, true);
newTable.setName("WonderfulLife");
newTable.setShowFilterButton(false);
// Either of the following two statements will work here but not further below after the table is manipulated
dataRange.getFormat().autofitColumns();
//newTable.getRange().getFormat().autofitColumns();
// This also works
//dataRange.getFormat().setColumnWidth(120);
newTable.setPredefinedTableStyle('TableStyleLight1');
// Get the column names
const ActorName = newTable.getColumn(3).getName();
const IMDBUrl = newTable.getColumn(9).getName();
// Set the formula newTable the fourth column to be that row's column 2 and 3 values.
const LinkedName = newTable.getColumn(10).getRangeBetweenHeaderAndTotal();
LinkedName.setFormula(`=HYPERLINK([#[${IMDBUrl}]],[#[${ActorName}]])`);
// For some reason the auto-fit doesn't work
// Error: Line 51: RangeFormat autofitColumns: You cannot perform the requested operation.
//dataRange.getFormat().autofitColumns();
//newTable.getRange().getFormat().autofitColumns();
// Best option I've found is to statically set the width but it's obviously not ideal.
//newSheet.getRange("J:J").getFormat().setColumnWidth(120);
newSheet.getRange("C:C").setColumnHidden(true);
newSheet.getRange("I:I").setColumnHidden(true);
}
function getData(): string[][] {
return [["First Name", "Last Name", "Actor", "Date of Birth", "Date of Death", "Gender", "Occupation", "Age", "URL", "Link"],
["George", "Bailey", "Jimmy Stewart", "5/20/1908", "7/2/1997", "Male", "Bank President", "89", "https://www.imdb.com/name/nm0000071", ""],
["Mary", "Hatch/Bailey", "Donna Reed", "1/27/1921", "1/14/1986", "Female", "Eternal Optimist", "65", "https://www.imdb.com/name/nm0001656", ""],
["Harry", "Bailey", "Todd Karns", "1/15/1921", "2/5/2000", "Male", "War Hero", "79", "https://www.imdb.com/name/nm0439851", ""]];
}
The autofitColumns() method works on line 34 but if I try to comment it out and run it later after defining a new link column value, it fails. I've tried various permutations of it but can't get it to work.
I tried to autofit the columns after defining a new column value but received an error. If the same code is run earlier it will work but won't include the new column's size in the resize.

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.

Excel Office Script not clearing cells. Getting unreachable code detected (7027) error

I have this script which works all except for the clearing of the B4:B120 area "// Clear the "Margin Updates" column." section which is greyed out for some reason):
function main(workbook: ExcelScript.Workbook): ReportImages {
// Recalculate the workbook to ensure all tables and charts are updated.
workbook.getApplication().calculate(ExcelScript.CalculationType.full);
// Get the data from the "Target Margins - FHM" table. (name of Excel tab, not name of table)
let sheet1 = workbook.getWorksheet("Sheet1");
const table = workbook.getWorksheet('Target Margins - FHM').getTables()[0];
const rows = table.getRange().getTexts();
// Get only the Product Type and "Margin Update" columns, then remove the "Total" row.
const selectColumns = rows.map((row) => {
return [row[0], row[1]];
});
// Delete the "ChartSheet" worksheet if it's present, then recreate it.
workbook.getWorksheet('ChartSheet')?.delete();
const chartSheet = workbook.addWorksheet('ChartSheet');
// Add the selected data to the new worksheet.
const targetRange = chartSheet.getRange('A1').getResizedRange(selectColumns.length - 1, selectColumns[0].length - 1);
targetRange.setValues(selectColumns);
// Get images of the chart and table, then return them for a Power Automate flow.
const tableImage = table.getRange().getImage();
return { tableImage };
// Clear the "Margin Updates" column.
const targetSheet = workbook.getActiveWorksheet();
const getRange = targetSheet.getRange("B4:B120");
getRange.clear(ExcelScript.ClearApplyTo.contents);`
}
// The interface for table and chart images.
interface ReportImages {
tableImage: string
}
The code copies the data in sections of the A and B columns (which constitute a table) and sends an email via Power Automate flow. Unfortunately, I need the section of the B column to be clear of values (not formatting or style) after which this flow is not doing.
I'd greatly appreciate help with this problem.
Thank you.
#cybernetic. nomad:
When I try using Range ("B4:B120").Clear I receive
unreachable code detected (7027)
and
and "cannot find name 'Range' (2304)
Office Script Range Clear Error
In JavaScript, the function exits as soon as the return keyword is evaluated. That's why it's saying your code is unreachable. So you have to restructure your code so that the return happens at the end. So you can update your code to look something like this:
// Clear the "Margin Updates" column.
const targetSheet = workbook.getActiveWorksheet();
const getRange = targetSheet.getRange("B4:B120");
getRange.clear(ExcelScript.ClearApplyTo.contents);
return { tableImage };

Excel office script - copy a row to a new tab if cell is NOT empty

I have created a script that will move an entire row to another tab on the worksheet if certain text is entered into a selected cell.
I want to be able to do this if the cell is not empty rather than having certain text and I would like the row to be deleted all except the first column.
The script is below and works really well, I'm not great at coding and managed to cobble this together from some other scripts i found but i now can't manage to edit it to fit this new task.
I tried using Javascript not equals signs and other symbols and can remove rows that are empty but i can't seem to make it work.
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]);
});
}
If I understand your question correctly, you are currently filtering the table if the value = "Y" (the value assigned to FILTER_VALUE). This part is happening here:
if (dataRows[i][FILTER_COLUMN_INDEX] === FILTER_VALUE) {
You'd like to update this line from checking if the cell value is Y to checking if the cell value is not empty. To do this, you can update this line like so:
if (dataRows[i][FILTER_COLUMN_INDEX] as string !== "") {

How to write array in excel sheet using excel4node

I am creating an excel file by using Excel4node package.
by using this code
// Require library
var excel = require('excel4node');
// Create a new instance of a Workbook class
var workbook = new excel.Workbook();
// Add Worksheets to the workbook
var worksheet = workbook.addWorksheet('Sheet 1');
var worksheet2 = workbook.addWorksheet('Sheet 2');
// Create a reusable style
var style = workbook.createStyle({
font: {
color: '#FF0800',
size: 12
},
numberFormat: '$#,##0.00; ($#,##0.00); -'
});
// Set value of cell A1 to 100 as a number type styled with paramaters of style
worksheet.cell(1,1).number(100).style(style);
// Set value of cell B1 to 300 as a number type styled with paramaters of style
worksheet.cell(1,2).number(200).style(style);
// Set value of cell C1 to a formula styled with paramaters of style
worksheet.cell(1,3).formula('A1 + B1').style(style);
// Set value of cell A2 to 'string' styled with paramaters of style
worksheet.cell(2,1).string('string').style(style);
// Set value of cell A3 to true as a boolean type styled with paramaters of style but with an adjustment to the font size.
worksheet.cell(3,1).bool(true).style(style).style({font: {size: 14}});
workbook.write('Excel.xlsx');
by using this code creating an excel sheet now what I want is.
I want to write the array in the excel sheet.
worksheet.getCell('A1').value = 's.no';
by using the code. it is writing the data to the sheet but it is writing the data by cell by cell.
it takes to much of time to write the array in excel sheet
data=[{s.no:1,Name:'xxx',Age:'22'},
{s.no:2,Name:'yyy',Age:'12'},
{s.no:3,Name:'zzz',Age:'32'}]
I want to write the array in the excel sheet.
workbook.write('Excel.xlsx',data);
I given like this but this also not working.
can anyone resolve this.
why you don't use
sheet.cell(row , col ).string(`your value`).style(`your style`);
What I would do is create an excel4node layer. Something to the effect of:
// the type arg is the cell write method you want to use such as string, number, or formula
const writeCell(wb, ws, row, col, value, type, style){
ws.cell(row, col)[type](value).style(wb.createStyle(style))
}
const xl = require('excel4node')
const wb = xl.createWorkbook()
// TODO: track your row and column indices with arrays. let's assume that we are
// in the outer array and the inner array or row array is called "data"
// this would write out a row of data
data.forEach((d, idx) => {
writeCell(wb, ws, rowIdx, idx + 1, d.value, d.type, d.style)
}
const array_elements = [{
fullname: "name 1",
game1point: 10,
game2point: 12
},
{
fullname: "name 2",
game1point: 15,
game2point: 17
},
]
let startRow = 3;
for (let i = 0; i < array_elements.length; i++) {
// FULLNAME - FIRST COLUMN
worksheet.cell(startRow + i, 1).string(array_elements[i].fullname);
// SECOND COLUMN
worksheet.cell(startRow + i, 2).number(array_elements[i].game1point);
// THIRD COLUMN
worksheet.cell(startRow + i, 3).number(array_elements[i].game2point);
gameCol++;
}

Assign an array of values to a range

I want to initiate a range of worksheet with an array of values. I have the following code.
function initiate(address, values) {
return Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = sheet.getRange(address);
var range.values = values;
return ctx.sync()
}
}
My tests show that this works only when values has exactly the same dimension (ie, height, width) as address, or when values is a single value. Otherwise, there will be an error.
Could anyone confirm that?
If so, I need to adjust address to suit the dimension of values. It is easy to get the dimension of values, but I cannot find a function to get a range from eg, 1 top-left cell + 1 number of rows + 1 number of columns, or 1 top-left cell and 1 bottom-right cell.
Am I missing something?
As CompuChip said, there is not (yet) an API for resizing a range to a particular absolute size, though it is forthcoming.
That said: if you have an array, just start with a single cell and then resize it by array's row-count-minus-1 (i.e., array.length - 1), followed by columns-minus-1 (array[0].length - 1)
const values = [
[1, 2, 3],
["Hello", "Bonjour", "Привет"]
]
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const range = sheet.getRange("D3").getResizedRange(
values.length - 1, values[0].length - 1);
range.values = values;
await context.sync();
});
You can try this snippet live in literally five clicks in the new Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/Zlatkovsky/6bc4a7ab36a81be873697cab0fa0b653. See more info about importing snippets to Script Lab.
In VBA you would use Range.Resize. In OfficeJS there seems to be a function called getResizedRange which
[g]ets a Range object similar to the current Range object, but with its bottom-right corner expanded (or contracted) by some number of rows and columns.
Unfortunately it accepts the delta values, so you need to calculate the difference between the current range size and the target size.
For example, if you wanted to obtain a range of rows by cols cells, you could try something along the lines of
var originalRange = sheet.getRange(address);
var range = originalRange.getResizedRange(
rows - originalRange.rowCount, cols - originalRange.columnCount);

Resources