Forum Discussion
Excel conditional formula
A TypeScript option for the exercise only (my 3rd script - be gentle please)
NB: Do not use it with large ranges
// TypeScript: Color cells on condition:
// IF cell value in Range_to_color + Increment = any cell value in Range_of_references
// THEN color cell in Range_to_color
//
// ASSUMPTION: Range_of_references & Range_to_color consist of numbers only (empty cells OK)
function main(
workbook: ExcelScript.Workbook,
Range_of_references: string = "A2:F2",
Range_to_color: string = "A3:F3",
Increment: number = 5
) {
function normalizeAddress(address: string): string {
// Trim and convert to uppercase for consistent address validation
return address.trim().toUpperCase();
}
function isValidAddress(address: string): boolean {
// Basic regex to validate Excel-style range references (e.g., A1:B3, B10:C10...)
const regExcelRange: RegExp = /^[A-Z]+[1-9][0-9]*:[A-Z]+[1-9][0-9]*$/;
return regExcelRange.test(normalizeAddress(address));
}
// Validate the input address format (e.g., "A1", "B100", "AZ35")
if (!isValidAddress(Range_of_references) || !isValidAddress(Range_to_color)) {
// Execution stops on throw (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw)
throw new Error("Please enter valid range address(es) like A1:D1, C4:G4, ...");
}
// Get active worksheet and ranges
const worksheet = workbook.getActiveWorksheet();
const sourceRange = worksheet.getRange(Range_of_references);
const targetRange = worksheet.getRange(Range_to_color);
// Count columns in targetRange
const targetColumns: number = targetRange.getColumnCount();
// Flatten sourceRange (workaround to .flat() that raises a warning in Excel TS console)
const flattenedSource = sourceRange.getValues()
.reduce((acc, row) => acc.concat(row), []);
// Iterate cells in targetRange
for (let i = 0; i <= targetColumns; i++) {
const currentCell = targetRange.getCell(0, i);
// Invoking read methods inside of a loop could lead to slow perf.:
const currentValue = Number(currentCell.getValue());
// Iterate flattenedSource
for (let j = 0; j <= targetColumns; j++) {
// If currentValue (in targetRange) + Increment = current flattenedSource value...
if (flattenedSource[j] === currentValue + Increment) {
// Color the cell in targetRange
currentCell.getFormat().getFill().setColor("#C1F0C8");
break
}
}
}
}
- SergeiBaklanOct 30, 2025Diamond Contributor
Another point it's hard to test with regex all valid addresses, from A1 to XFD1048576. Expression will be too complex. However, OfficeScript checks itself if we use correct range, thus we may rely on it and use try/catch. In addition it's only to add our own rule which doesn't allow to run script with single cell ranges. Something like
function main( workbook: ExcelScript.Workbook, Range_of_references: string = "A2:F2", Range_to_color: string = "A3:F3", Increment: number = 5 ) // see https://techcommunity.microsoft.com/discussions/excelgeneral/excel-conditional-formula/4464321 { const worksheet = workbook.getActiveWorksheet() if (!Range_of_references.includes(":") || !Range_to_color.includes(":")) { throw new Error(`Keep ":" in range addresses`) } try { worksheet.getRange(Range_of_references) worksheet.getRange(Range_to_color) } catch (error) { console.log(`Incorrect (${Range_of_references}) or (${Range_to_color}) address. Please inspect and run again.`) return } const sourceRange = worksheet.getRange(Range_of_references) const targetRange = worksheet.getRange(Range_to_color) const flattenedSource = sourceRange.getValues()[0] const incremented = targetRange.getValues()[0].map(x => +x + Increment) targetRange.getFormat().getFill().clear() Array.from(incremented.keys()) .filter(n => incremented .map(x => flattenedSource.includes(x))[n]) .map(m => targetRange.getCell(0, m).getFormat().getFill().setColor("lightgreen")) }- LorenzoOct 30, 2025Silver Contributor
Agreed, regex isn't the appropriate approach (did it for the exercise) & your try...catch approach should do a better jon
Good training material for me :) who started with JS/TS a few days ago. Very concise approach:
const flattenedSource = sourceRange.getValues()[0] const incremented = targetRange.getValues()[0].map(x => +x + Increment) targetRange.getFormat().getFill().clear() Array.from(incremented.keys()) .filter(n => incremented .map(x => flattenedSource.includes(x))[n]) .map(m => targetRange.getCell(0, m).getFormat().getFill().setColor("lightgreen"))- SergeiBaklanOct 31, 2025Diamond Contributor
Another comment, with
const flattenedSource = sourceRange.getValues()[0]I assumed we work with rows, otherwise above doesn't work. In general , flat() works even if the editor reports the problem. Both of these
const v = workbook.getActiveWorksheet().getUsedRange().getValues() const flattenedA: (number | string | boolean) [] = v.flat() const flattenedB: (number | string | boolean) [] = [] for (let row of v) { for (let cell of row) { flattenedB.push(cell) } }return exactly the same array.
- SergeiBaklanOct 30, 2025Diamond Contributor
Great approach. Here is with slightly modified second part
function main( workbook: ExcelScript.Workbook, Range_of_references: string = "A2:F2", Range_to_color: string = "A3:F3", Increment: number = 5 ) // see https://techcommunity.microsoft.com/discussions/excelgeneral/excel-conditional-formula/4464321 { function normalizeAddress(address: string): string { // Trim and convert to uppercase for consistent address validation return address.trim().toUpperCase(); } function isValidAddress(address: string): boolean { // Basic regex to validate Excel-style cell references (e.g., A1:B3, B10:C10...) const regex = /^[A-Z]+[1-9][0-9]*:[A-Z]+[1-9][0-9]*$/; return regex.test(normalizeAddress(address)); } // Validate the input address format (e.g., "A1", "B100", "AZ35") if (!isValidAddress(Range_of_references) || !isValidAddress(Range_to_color)) { // Execution stops on throw (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw) throw new Error("Please enter valid range address(es) like A1:D1, C4:G4, ..."); } // Get active worksheet and ranges const worksheet = workbook.getActiveWorksheet(); const sourceRange = worksheet.getRange(Range_of_references); const targetRange = worksheet.getRange(Range_to_color); ///////////////////////////////////////////////////////////////////// // Another variant const flattenedSource = sourceRange.getValues()[0] const incremented = targetRange.getValues()[0].map(x => +x + Increment) targetRange.getFormat().getFill().clear() Array.from( incremented.keys()) .filter(n => incremented .map(x => flattenedSource.includes(x))[n]) .map( m => targetRange.getCell(0, m).getFormat().getFill().setColor("lightgreen")) // end of another variant ///////////////////////////////////////////////////////////////////////// /* // Count columns in targetRange const targetColumns: number = targetRange.getColumnCount(); // Flatten sourceRange (workaround to .flat() that raises a warning in Excel TS console) const flattenedSource = sourceRange.getValues() .reduce((acc, row) => acc.concat(row), []); // Iterate cells in targetRange for (let i = 0; i <= targetColumns; i++) { const currentCell = targetRange.getCell(0, i); // Invoking read methods inside of a loop could lead to slow perf.: const currentValue = Number(currentCell.getValue()); // Iterate flattenedSource for (let j = 0; j <= targetColumns; j++) { // If currentValue (in targetRange) + Increment = current flattenedSource value... if (flattenedSource[j] === currentValue + Increment) { // Color the cell in targetRange currentCell.getFormat().getFill().setColor("lightgreen") // ("#C1F0C8"); break } } } */ }