Select a discontiguous range - excel

I'm building an office-js add-in for Excel. I need to select two non-adjacent cells (e.g A1 and C3). The following code works to select the multi-cell range starting at A1 and ending at C3.
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getItem('sheet1');
var range = sheet.getRange('a1:c3');
range.select();
return ctx.sync();
});
However, I'm looking to select only the two cells (A1 and C3). In VBA the syntax is
worksheets("sheet1").range("a1,c3").select
But I cannot find anything analogous in office-js. I've tried as similar syntax with office-js:
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getItem('sheet1');
var range = sheet.getRange('a1,c3');
range.select();
return ctx.sync();
});
but it fails with: {"code":"InvalidArgument","message":"The argument is invalid or missing or has an incorrect format.","errorLocation":"Worksheet.getRange"}

An API to work with discontinuous ranges is not yet available in Office.js. We are working on it and are finalizing the design right now. In the meantime, you will have to create separate range objects for the two cells and operate on each of them with duplicate commands.

Actually on the latest insiders fast (16.0.9327.2006 ) deployed just a few days ago you can actually try our implementation for Areas (aka discontinuous ranges. )
please make sure to use our preview cdn to test this.(https://appsforoffice.microsoft.com/lib/beta/hosted/office.js)
but basically you can do things like:
function run() {
return Excel.run(function (context) {
var range = context.workbook.getSelectedRange();
range.load("address");
return context.sync()
.then(function () {
console.log("The range address was \"" + range.address + "\".");
});
});
}
and you will see that if you select a non-continuous range you will get something like: "Sheet1!C6:C14,Sheet1!F12:H22".
you can pass a similar string on the getRange method to create an Area and simultaneously format it etc.
Please give it a try and send us your feedback! thanks!

context.workbook.getSelectedRange()
is used for contiguous ranges.
If you want to get the range for a discontiguous range you should use:
context.workbook.getSelectedRanges()

Related

Excel Javascript API - join range or complex range selection

In Excel macro you can do something simple like:
Range("C8:G8,C12:H12,C19:I19").Value = 1
this gives me an opportunity to create a complex range selection and reduce the number of api calls and sync queue.
But when I do
const range = activeWorkSheet.getRange("C8:G8,C12:H12,C19:I19");
range.format.fill.color = "yellow";
I get
InvalidArgument: The argument is invalid or missing or has an
incorrect format.
There is also no getJoinRange or getUnionRange I could use.
Is there a workaround? I am having some performance issue as I do thing like format a row based on odd/even.
There is a beta feature forthcoming, which will allow multi-area ranges. Its syntax is still TBD, it might be exactly what you wrote (with range being allowed to be a multi-area range), or perhaps we'll keep Range a single contiguous object and have parallel methods like worksheet.getMultiAreaRange("C8:G8, C12:H12, C19:I19) to do what you would like.
Also, what version of Office do you have and are you on Insider Fast, by any chance?
Re. performance, can you post your exact use-case as a minimal snippet? There may be some optimizations you can do, even barring multi-area ranges.
Update
If all you're doing is a 3x3 or a 5x5 (i.e., not something super-huge), you don't need multi-area support. You can just do:
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const rowCount = 5;
const columnCount = 5;
const range = sheet.getRangeByIndexes(0, 0, rowCount, columnCount);
for (let row = 0; row < rowCount; row = row + 2) {
range.getRow(row).format.fill.color = "purple";
}
await context.sync()
});

get row from range in current worksheet?

I can not get tables name in active worksheet. I have a drop-down that will be populated with worksheets in workbook. I have another drop-down that should get all columns names(header) in selected worksheets. Somehow range.address has "sheet1!2:2" .
Here is code that I used:
function getRow(worksheetName) {
Excel.run(function (ctx) {
// Queue a command to write the sample data to the worksheet
// at moment i have only one worksheet named "Sheet1"
var range = ctx.workbook.worksheets.getItem(worksheetName).getRange().getRow(1);
range.load('address');
// Run the queued-up commands, and return a promise to indicate task
//completion
return ctx.sync().then(function () {
console.log(range.address); // prints Sheet1!2:2
})
})
.catch(errorHandler);
}
Here is a link to spreadsheet that I used for testing.
Any clue what i am doing wrong here?
I'd suggest that you replace getRange() with getUsedRange() instead, as shown here:
function getRow(worksheetName) {
Excel.run(function (ctx) {
var range = ctx.workbook.worksheets.getItem(worksheetName).getUsedRange().getRow(1);
range.load('address');
return ctx.sync().then(function () {
console.log(range.address);
})
})
.catch(errorHandler);
}
Rick Kirkham's comment above is correct. In the file that you've linked to, there is no table object -- although the worksheet does contain several rows/columns of data, that data is not explicitly contained inside a table.
Are you able to manually manipulate this file? If yes, then you can create a table object for the data by doing the following:
Select the data you want to be in the table.
With that data selected, choose the Table button (on the Insert tab).
Verify inputs in the Create Table prompt and choose OK.
If you are not able to manually manipulate this file, then you can create the table (from the range of data in your worksheet) by using the Office JavaScript API, as described here: https://dev.office.com/docs/add-ins/excel/excel-add-ins-tables#convert-a-range-to-a-table.

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);

Write array formulas

To copy single formulas from an area to another, we could use directly rangeTarget.formulas = rangeSource.formulas once rangeSource.formulas is loaded. However, this method does not seem to apply to array formulas.
For example, A1:A8 contains an array formula {=B1:B8+10}, and the following code attempts to copy this array formula to C1:C8:
function test () {
Excel.run(function (ctx) {
var r0 = ctx.workbook.worksheets.getItem("Sheet1").getRange("A1:A8");
r0.load(["formulas"]);
return ctx.sync().then(function () {
var r1 = ctx.workbook.worksheets.getItem("Sheet1").getRange("C1:C8");
r1.formulas = r0.formulas;
});
});
}
Here is the result (Excel Online and Excel for Windows):
So does anyone know what's the correct way to copy an array formula?
Additionally, I don't know how to enter an array formula manually in Excel Online, the shortcut Ctrl+Alt+Enter does not seem to work (on Mac keyboard).
Array Formulas are not currently supported by the Office.js Excel APIs. However, please feel free to suggest / vote for it on https://officespdev.uservoice.com/
~ Michael Zlatkovsky, Developer on Office Extensibility Team, MSFT

Force the calculation of a cell by JavaScript API

I try to run a sample from this page in a workbook:
Excel.run(function (ctx) {
var sheetName = "Sheet1";
var rangeAddress = "F5:G7";
var numberFormat = [[null, "d-mmm"], [null, "d-mmm"], [null, null]]
var values = [["Today", 42147], ["Tomorrow", "5/24"], ["Difference in days", null]];
var formulas = [[null,null], [null,null], [null,"=G6-G5"]];
var range = ctx.workbook.worksheets.getItem(sheetName).getRange(rangeAddress);
range.numberFormat = numberFormat;
range.values = values;
range.formulas= formulas;
range.load('text');
return ctx.sync().then(function() {
console.log(range.text);
});
}).catch(function(error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
When the calculation mode is automatic, the result of G7 is 367. By contrast, when the calculation mode is manual, the result of G7 is 0; that means the formula =G6-G5 is not re-evaluated.
Does anyone know if there is a way to force the calculation of G7 by JavaScript API, under manual mode?
One way we could image would be to change the calculation mode (to automatic), however, calculationMode is read-only here.
Another way would be to run the method calculate(calculationType: string), but it recalculates all currently open workbooks in Excel, which is quite costly, and may not be what we want to do.
I feel it would be quite restricting if such a fundamental operation is not possible regardless of workarounds...
At the moment the option way to manually trigger calculation is to use the calculate() method against the entire workbook. It shouldn't trigger this across workbooks however, only the workbook held by this application instance. The application object only references the single Excel instance running the add-in.

Resources