Office.js | Excell add in | get selected cell address on click - excel

I am working on a Excel Web Add-In using Office.js. I need to get selected cell's Address with below scenario:
Open the Excel, Don't load add-in, click on any Cell in excel worksheet area. eg:A4
Load the add-in.
Again click on the same cell number eg:A4 that was highlighted on worksheet area as before loading add-in we selected.
Its not triggering the below code:
Office.context.document.addHandlerAsync(Office.EventType.DocumentSelectionChanged,
function (eventArgs) {
Excel.run(function (ctx) {
var range = ctx.workbook.getSelectedRange();
range.load(['address', 'values']);
return ctx.sync().then(function () {
showNotification("", range.values[0][0] + " Address:" + range.address);
});
});
});
NOTE: If I select cell Id other than previously selected cell id eg.A5 its working. Even again if I try to select from cell id A5 to previously selected cell id A4 its working.
Only if we try to select same cell its not triggering the event.
Spend lots of time can some one please help or its limitation in microsoft excel?

I think this is by design, as you are listening to SelectionChanged event, if the current selection is A4, and you click A4, it would not trigger the event as you didn't change the selection.
I am not sure your scenario, if you want to show the current selection, you could add the code before register the event. here is a sample code
async function run() {
await Excel.run(async (context) => {
var range = context.workbook.getSelectedRange();
range.load();
await context.sync();
console.log(range);
Office.context.document.addHandlerAsync(Office.EventType.DocumentSelectionChanged, showSelection);
});
}
async function showSelection(){
Excel.run(async (context) => {
var range = context.workbook.getSelectedRange();
range.load();
await context.sync();
console.log(range);
});
}

Related

Excel.js custom function caller cell number format

I'm trying to number format the caller cell for a custom function, specifically to replace the scientific notation with a numeric format for big numbers and then auto fit the column width.
An idea is to check the cell text for presence of "E", but the issue is that the formatting code seems to run before the result is written to the cell (which kind of makes sense, honestly), so I'm doing a comparison and set the cell format accordingly. Setting the cell format works fine (it doesn't need the result written to the cell), but auto fitting the column width doesn't.
Here is the custom function code:
getData returns a number (or an error string) from an API call
formatNumber should set the cell number format and autofit the column width, based on the returned number.
async function Test(symbol, metric, date, invocation) {
const address = invocation.address;
return await getData(symbol, metric, date)
.then(async (result) => {
if (!isNaN(result) && result > 99999999999) {
await formatNumber(address);
}
return result;
})
.catch((error) => {
console.log("error: " + error);
return error;
});
}
Here is the formatNumber code.
range.text returns #BUSY, which means the data is still retrieved from the API when the function runs. Due to this, autofitColumns will set the column size based on "#BUSY" string length.
async function formatNumber(address) {
await Excel.run(async (context) => {
const formats = [["#,##0"]];
const range = context.workbook.worksheets.getActiveWorksheet().getRange(address);
range.load("text");
await context.sync();
console.log("range.text: " + range.text);
range.load("numberFormat");
await context.sync();
range.numberFormat = formats;
range.format.autofitColumns();
await context.sync();
});
}
Any ideas?
Thank you for your time,
Adrian
The custom functions return value will be set to the cell after the function is returned.
I suggest your add-in register an onChanged event handler on the worksheet, and call format.autofitColumns() to handle the event;

Set 'Fit All Columns on One Page' via Excel JavaScript API

I'm looking to set the "Fit All Columns on Page" print option via the Excel JavaScript API. It's very similar to this question which uses VBA -->
How to set 'Fit all columns on one page' in print tab
The Office 365 Online Automate Scripts helped get me on the right path.
Docs:
https://learn.microsoft.com/en-us/javascript/api/excel/excel.pagelayoutzoomoptions?view=excel-js-preview
https://learn.microsoft.com/en-us/javascript/api/excel/excel.pagelayout?view=excel-js-preview#excel-excel-pagelayout-zoom-member
Code:
await Excel.run(async (context) => {
var ws = context.workbook.worksheets.getActiveWorksheet();
var PageLayoutZoomOptions_Obj = {
'horizontalFitToPages': 1,
'verticalFitToPages': 0,
}
ws.pageLayout.zoom = PageLayoutZoomOptions_Obj
await context.sync();
});
Note: I had issues using/including scale so I just left it out.

How to copy a Google sheet and get data validation to stay intact?

I have a Google spreadsheet template with two sheets, Data Entry and Data Validation. The Data Validation sheet has a number of columns with valid values for matching columns on the Data Entry sheet. Everything works as expected. I need to copy these two sheets to a Sheet Under Test (SUT). I am using the sheets API to copy both sheets. I copy the Data Validation sheet first and then the Data Entry sheet. Here's the code and this appears to work.
const request = {
spreadsheetId :fromSpreadsheetId,
sheetId : fromSheetId,
resource:{
destinationSpreadsheetId: toSpreadsheetId,
},
}
const result = await _sheetService.spreadsheets.sheets.copyTo(request)
On my SUT, both sheets appear and the Data entry sheet has all the expected dropdowns and they all have the proper values. Seems perfect. The problem is when you select an item from any drop down in any column it selects and enters the proper value and then adds a red triangle and the message that an invalid value has been entered. If the column has the rejection setting then the value is removed and the error dialog appears.
The image shows two cells where I have already selected Video Course from the drop down.
If I go in and reselect column where I want validation, use Data→DataValidation… and just hit the Save button that column starts working, so it would appear everything came across correctly but the sheet doesn't think so. Is there any programmatic way to force the above process that I did manually? Is there something else I need to do in the sheets.copyTo method to make this work properly?
EDIT
This project is written in Node.js with a combination of TypeScript and JavaScript. The lower level code for talking to the Sheets API and copying the sheet between spreadsheets can be found in this github file. The method is copySheetFromTo and it's at the bottom of the file.
A sample source sheet with public view permissions
A sample destination sheet with public edit permissions
The integration test that used the above two files to copy the sheets is at the bottom of the file and has 'DEBUGGING TEST' at the beginning of the name (starts at line 209)
You want to copy the sheet including Data Validation.
When the copied sheet is used, an error occurs at the drop down menu of Data Validation.
You want to remove this error.
If my understanding is correct, how about this answer? In this answer, in order to remove the error, I overwrite the Data Validation of the copied sheet as a workaround. Please think of this as just one of several workarounds.
The flow for your situation is as follows.
Flow:
Retrieve all data validations from the sheet of Data Entry in the source Spreadsheet ("DataValidationTest") using the spreadsheet.get method.
Copy the sheets of Data Entry in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
After the sheets of Data Entry was copied, rename the sheet name from Copy of Data Entry to Data Entry.
Then, overwrite the retrieved data validations to the sheet of Data Entry using the spreadsheet.batchUpdate method.
In this case, the structure of data validations retrieved by the spreadsheet.get method is almost the same with the structure for the spreadsheet.batchUpdate method. This workaround used this.
Copy the sheets of Data Validation in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
Rename the sheet name of Copy of Data Validation to Data Validation.
Sample script:
When you test this script, please set the variables. And I think that sheet of sheet.spreadsheets.get(), sheet.spreadsheets.batchUpdate() and sheet.spreadsheets.sheets.copyTo() is the same with sheetOps of your script.
const srcSpreadsheet = "###"; // Please set this.
const tempDestSheetId = "###"; // Please set this.
const srcDataEntrySheetId = 0; // Please set this.
const srcDataValidationSheetId = 123456789; // Please set this.
let dataValidation = await sheet.spreadsheets.get({
spreadsheetId: srcSpreadsheet,
ranges: ["Data Entry"],
fields: "sheets/data/rowData/values/dataValidation"
});
let data = dataValidation.data.sheets[0].data;
let rows = [];
for (let i = 0; i < data.length; i++) {
if (data[i].rowData) {
rows = data[i].rowData;
break;
}
}
sheet.spreadsheets.sheets.copyTo(
{
spreadsheetId: srcSpreadsheet,
sheetId: srcDataEntrySheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
},
(err, res) => {
sheet.spreadsheets.batchUpdate(
{
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: { sheetId: res.data.sheetId, title: "Data Entry" }
}
},
{
updateCells: {
rows: rows,
range: { sheetId: res.data.sheetId },
fields: "dataValidation"
}
}
]
}
},
(er, re) => {
if (err) {
console.error(er);
return;
}
console.log(re.data);
}
);
}
);
let result1 = await sheet.spreadsheets.sheets.copyTo({
spreadsheetId: srcSpreadsheet,
sheetId: srcDataValidationSheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
});
let result2 = await sheet.spreadsheets.batchUpdate({
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: {
sheetId: result1.data.sheetId,
title: "Data Validation"
}
}
}
]
}
});
console.log(result2.data);
Note:
This script supposes that Sheets API has already been able to be used.
This is the simple modified script for showing the flow of workaround. So please modify this for your situation.
As an important point, in order to overwrite the data validations to the copied sheet of Data Entry, it is required to execute it after the copy of the sheet of Data Entry was completely finished. So I modified the script like above.
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.get
Method: spreadsheets.sheets.copyTo

How to unlock only specific cells from write protected worksheet using excel javascript API

I am using office 365 and Excel online (Build 16.0.9403.1875).
and I am creating Microsoft Excel online Add-ins, using Excel javascript API.
I have a requirement like write protection for entire sheet except few cells (Range).
So, I referred Format protection API, but while debugging, the ‘locked’ property is of no effect in action.
I referred, stack over flow too with this link
And others too
As, I have already tried with the suggestion given by above forum links.
I just expecting reply by working snippet of code.
My code samples are below
Excel.run(function (ctx) {
//Worksheet
var sheet = ctx.workbook.worksheets.getItem("Sheet1");
//Entire Range
var entireRange = sheet.getRange();
entireRange.format.protection.locked = false;
//Specific Range
var range = sheet.getRange("A1:B5");
return ctx.sync()
.then(() => {
//Set specific range "locked" status to true.
range.format.protection.locked = true;
})
.then(ctx.sync)
.then(() => {
//Protect Entire sheet
sheet.protection.protect({
allowInsertRows: false,
allowDeleteRows: false
});
});
}).catch(errorHandler);
Thanks.

Append the formula of the selected cell

I want to realise the following scenario: a user selects a cell holding a formula, clicks on the test button of my add-in, then my test function reads the formula of the selected cell, append +RAND() to it, and write it back to the workbook.
The following code reads well the formula of the selected cell, but it does not write back well. I am not sure if (the second) return ctx.sync() is correctly used.
Additionally, I don't know if I should use getSelectedDataAsync and setSelectedDataAsync (rather than getSelectedRange) in the whole scenario.
Could anyone help?
(function() {
"use strict";
Office.initialize = function(reason) {
$(document).ready(function() {
app.initialize();
$('#test').click(test);
});
}
;
function test() {
Excel.run(function(ctx) {
var selectedRange = ctx.workbook.getSelectedRange();
selectedRange.load(["formulas"]);
return ctx.sync().then(function() {
console.log(selectedRange.formulas[0][0]);
var x = selectedRange.formulas[0][0] + "+RAND()";
selectedRange.formulas[0][0] = x;
return ctx.sync();
})
}).then(function() {
console.log("done");
}).catch(function(error) {
console.log("Error: " + error);
});
}
})();
The bug is that you are trying to assign to an individual element in the formula context object. Instead use:
selectedRange.formulas = x;
or
selectedRange.formulas = [[x]];

Resources