Unlock individual cells before protect sheet - office-scripts

There's another question addressing how to protect the sheet but doesn't show how to unlock individual cells.
I can't just start recording & create a script (which usually show how it's done) because apparently, excel online still doesn't support protecting sheets from the web UI.

This took me a while to find out, so I'm answering my own question here for the benefit of others with the same need.
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getWorksheet("Sheet1");
// will not let you lock/unlock cells if protected
sheet.getProtection().unprotect();
// just checking
let locked = sheet.getRange("B9").getFormat().getProtection().getLocked()
console.log('value of locked: ', locked)
// unlock
sheet.getRange("B9").getFormat().getProtection().setLocked(false)
// sanity check
locked = sheet.getRange("B9").getFormat().getProtection().getLocked()
console.log('locked value after set to false: ',locked)
sheet.getProtection().protect({
allowFormatCells: false
//allowFormatCells: true
});
}

Related

Excel custom functions show BUSY! but async function is not called

I have an Excel Add-in having custom functions and taskpane. My client created a workbook having multiple sheets using my custom functions approximately 3500+ function calls in current workbook. When a user opens this workbook, I need to recalculate sheet so that only my functions are recalculated. To achieve this task, I have performed following steps.
Loop on sheets in workbook.
Search each sheet for my formula using worksheet.findAllOrNullObject() function.
if search result is not NullObject, then call ranges.calculate(). Which should trigger function calls.
var sheets = context.workbook.worksheets;
sheets.load("items/name");
await context.sync();
for (var i = 0; i < sheets.items.length; i++) {
var sheet = sheets.items[i];
const foundRanges = sheet.findAllOrNullObject(FORMULA_DATA[formula], {
completeMatch: false,
matchCase: false
});
await context.sync();
if (!foundRanges.isNullObject)
foundRanges.calculate();
await context.sync();
}
}
Problem I am facing that when I call recalculate function, all cells referring to these functions show BUSY! which means that my function have not resolved promise yet, but no function is actually called. I added break points at start of each function during debugging but no code stops there (I a change a single cell then breakpoint is hit).
I enabled run-time logging and it has entries for each call begin but no end call entry.
Also one of the cell reference is passed to all functions and if I change its value, then all function calls are made properly and it shows result as desired and logfile contains entries for begin and end for all calls.
After thoroughly investigating the issue, I have come to conclusion that this client was using other Add-in which had same function names and it was saved in workbook. When I recalculated the sheet range areas, it was trying to call functions from those old Add-in which was uninstalled earlier. Therefore all cells were showing BUSY! and since code was not there in excel these promises were never resolved. Once I removed all taskpanes from workbook and re-opened it this problem does not appeared again.
To save Taskpanes info
Office.addin.setStartupBehavior(Office.StartupBehavior.load);
To Remove Taskpanes information, use File => Info => Check for Issues => Inspect Document => Inspect.
After it displays results , Task pane Add-ins => Remove All.

Find out when a row has been edited by user

I need to know when a row edit ended, (this means when cell editor moved to another row or cell editor was hidden with any changes), to send edited data to service. I have cellEditing and cellEdited events, but sending data after each cellEdited is too expensive.
May be I can use, for example, cellEdited with some additional checks, but I can't figure out how. Or may be there is a better way to do it?
You can call the isEdited on a cell component to see if it has been edited in the past, so in the cellEdited callback you could cycle over the cells in the row to ses if they have all been edited:
dataEdited:function(cell){
var cells = cell.getRow().getCells();
var allEdited = true;
cells.forEach(function(cell){
if(!cell.isEdited()){
allEdited = false;
}
});
if(allEdited){
//do something when all cells have been edited
}
}
you can also cleare the edited flag on a cell using the 'clearEdited' function on the cell component, this could be useful to allow the user to edit again after you have performed whatever you need to after the row edit:
cell.clearEdited();
I found a temporary solution for now.
First of all, I'am catching cellEdited, store current cell and starting a timer. This needs to define, if some editor will shown after current one. If timer is completed, I realize that row edition is ended and do my job.
Then I'am catching cellEditing, to check if row editing is continued. If cellEdition is fired, check timer existence, if it is, stop the timer and check: if current cell is in row with last edited cell, if it is, do nothing (row editing continues), if it is not, do my job (last row edit ended).
Here is part of my Angular controller with code:
editTimerId?: any;
lastEditedCell: Tabulator.CellComponent;
cellEditCompleted(cell: Tabulator.CellComponent) {
this.lastEditedCell = cell;
this.editTimerId = setTimeout(() => this.timerComplete(), 2000);
}
timerComplete() {
// Do the thing
}
cellEditingBegin(cell: Tabulator.CellComponent) {
if (this.editTimerId) {
clearTimeout(this.editTimerId);
this.editTimerId = null;
let lastRow = this.lastEditedCell.getRow();
if (cell.getRow() !== lastRow) {
// Do the thing
}
}
}
I think this solution is too complicated, so if someone have a better way, I'll be glad to see it.

Protect Worksheet in Office Scripts with Options

How can I protect a worksheet but allow the user to format the columns in Office Scripts? I have tried a few things but haven't had any success.
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getWorksheet("By Item");
sheet.getProtection().protect(ExcelScript.WorksheetProtectionOptions.allowFormatColumns);
}
Please see the attached link
https://learn.microsoft.com/en-us/javascript/api/office-scripts/excelscript/excelscript.worksheetprotection?view=office-scripts#protect-options--password-
The protect() method takes an object as argument for the 1st argument. See below.
I noticed that cell background/fill doesn't work even with this setting. All other formatting works such as font color, border, etc. That may be a bug that we'll follow-up on.
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getWorksheet("By Item");
sheet.getProtection().unprotect();
sheet.getProtection().protect({
allowFormatCells: true
});
}

Excel Office Add-In API Worksheet Protection Password

I have a question relating to Excel's worksheet protection...
The context is that I need to have different worksheets available for different user groups to edit but all groups must at least see all sheets e.g. usergroup1 can edit sheets two and three and parts of sheet one, usergroup2 can edit only sheet one.
I am able to set the FormatProtection (range.format.protection.locked = false;) accordingly and WorksheetProtection (worksheet.protection.protect();) to enable this but I don't appear to have the ability to set a password through the API against the Worksheet Protection? This means for example, that either group can simply click the Unprotect Sheet option in the review ribbon and edit the sheets that I don't want them to.
I've tried going through the below documentation but to no avail unfortunately.
http://dev.office.com/reference/add-ins/excel/worksheetprotection
https://github.com/OfficeDev/office-js-docs/blob/master/reference/excel/worksheetprotection.md
As an example, here is a function that I'd like to complete:
function CopyWorksheet() {
var newAddress;
Excel.run(function (ctx) {
var worksheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = worksheet.getUsedRange();
range.load();
// insert new worksheet
var newWorksheetName = "Copied_Sheet";
var newWorksheet = ctx.workbook.worksheets.add(newWorksheetName);
return ctx.sync().then(function () {
// copy the old values to the new worksheet
newAddress = range.address.substring(range.address.indexOf("!") + 1);
newWorksheet.getRange(newAddress).values = range.values;
newWorksheet.getRange(newAddress).formulas = range.formulas;
newWorksheet.getRange(newAddress).text = range.text;
// protect both worksheets
worksheet.protection.protect();
newWorksheet.protection.protect();
// requirement here to set a password so that no one can
// edit the worksheets by selecting 'Unprotect Sheet' in excel
// ...
})
.then(ctx.sync)})
.catch(function(error) {
console.log("Error: " + error);
});
}
Currently, I'm using Excel 2016 (desktop version). Is this possible to implement or have I missed some functionality that exists which can achieve the same result?
Thanks for your help.
Password-protection is not available in our APIs. You can protect the sheet to avoid casual edits, but you can't password-protect. The reason is that password-protection is not available on all endpoints (IIRC, there was an issue with Excel Online).
If you want to file a suggestion bug on UserVoice, you can see if we'd consider doing password-protection as a Desktop-only API. We have so far avoided doing those in Excel, but I do know that Word has done a few "WordApiDesktop" APIs. So depending on how much it's blocking your (and others') scenario, that might be an option. In which case you'd be able to password-protect and unprotect on desktop, but wouldn't be able to take those actions online.
There is an update for this issue: we now support password protection. Check https://learn.microsoft.com/en-us/javascript/api/excel/excel.workbookprotection?view=office-js#protect-password-

Google Doc referencing another sheet

After speaking to Google Enterprise Support they suggested I create a post on Stackoverflow.
I have a Google Doc sheet with a list of stores (sheet A) and I'm trying to reference another sheet (sheet B) to VOID specific stores.
What I'm going to accomplish is if a store name on the void sheet is entered into sheet A it will automatically convert the store name to VOID.
Google support believes an IF statement would be the beginning to the solution, they weren't able to help beyond this.
For anyone's time that comes up with a solution, I'd be happy to buy you a couple Starbucks coffees. Your support means a lot.
make it simple using Google Scripts. (Tutorial)
To edit scripts do: Tools -> Script Editor
and in the current add this function
EDIT:
Well, you need to make a trigger. In this case will be when the current sheet is edited
Here is the javascript
function onEdit(event) {
// get actual spreadsheet
var actual = event.source.getActiveSheet();
// Returns the active cell
var cellSheet1 = actual.getActiveCell();
// get actual range
var range = event.source.getActiveRange();
// get active spreadsheet
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// get sheets
var sheet_two = activeSpreadsheet.getSheetByName("Sheet2");
range.copyValuesToRange(sheet_two, range.getColumn(), range.getColumn(), range.getRow(), range.getRow());
// get cell from sheet2
var cell = sheet_two.getRange(range.getValue());
cell.setValue(cellSheet1.getValue());
// display log
Logger.log(cellSheet1.getValue());
}
if you want to test it you can check my spreadsheet, there you can check that all data thats is inserted in sheet1 will be copied to sheet2

Resources