I have written the following code to implement custom conditional formatting using Office JS -
function customFormatting() {
// Run a batch operation against the Excel object model
Excel.run(function (ctx) {
// Create a proxy object for the active worksheet
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
//Queue a command to write the sample data to the specified range
//in the worksheet and bold the header row
var range = sheet.getRange("A2:E9");
var conditionalFormat = range.conditionalFormats.add(Excel.ConditionalFormatType.custom);
conditionalFormat.custom.format.fill.color = "red";
conditionalFormat.custom.rule = {formula:">1001 && <5000"};
//Run the queued commands, and return a promise to indicate task completion
return ctx.sync();
})
.then(function () {
app.showNotification("Success");
console.log("Success!");
})
.catch(function (error) {
// Always be sure to catch any accumulated errors that bubble up from the Excel.run execution
app.showNotification("Error: " + error);
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
}
I am getting the following error while running the code -
Error: TypeError: Attempted to assign to readonly property.
(Revising my answer to describe 2 possible solutions, since I'm not clear on exactly which scenario matches what you're trying to achieve.)
Solution 1: Highlight cell when cell value meets criteria
In this first scenario, let's assume you have this table in the active worksheet, and your objective is to highlight the cell in column E of any row where the value in column E is between 1001 and 5000:
The following code uses conditional formatting to set fill color to yellow in column E when the cell value is between 1001 and 5000.
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = sheet.getRange("E2:E9");
var conditionalFormat = range.conditionalFormats.add(Excel.ConditionalFormatType.cellValue);
conditionalFormat.cellValue.format.fill.color = "yellow";
conditionalFormat.cellValue.rule = { formula1: "=1001", formula2: "=5000", operator: "Between" };
return ctx.sync()
.then(function () {
//app.showNotification("Success");
console.log("Success!");
})
})
.catch(function (error) {
//app.showNotification("Error: " + error);
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
After this code runs, the table looks like this:
Solution 2: Highlight entire row when a specific cell value in the row meets criteria
In this next scenario, let's assume you have this table in the active worksheet, and your objective is to highlight the entire row of data (columns A-E) whenever the value in column E of a row is between 1001 and 5000:
The following code uses conditional formatting to set fill color to yellow for the entire row of data (columns A-E), whenever the value in column E of a row is between 1001 and 5000.
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = sheet.getRange("A2:E9");
var conditionalFormat = range.conditionalFormats.add(Excel.ConditionalFormatType.custom);
conditionalFormat.custom.format.fill.color = "yellow";
conditionalFormat.custom.rule.formula = '=IF((AND($E2>1001, $E2<5000)),TRUE)';
return ctx.sync()
.then(function () {
//app.showNotification("Success");
console.log("Success!");
})
})
.catch(function (error) {
//app.showNotification("Error: " + error);
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
After this code runs, the table looks like this:
conditionalFormat.custom.rule is read only. This means that you can't create an object and assign it to conditionalFormat.custom.rule as your code is trying to do. Instead you have to assign values to each property of the rule. For example:
conditionalFormat.custom.rule.formula = '=IF(B8>INDIRECT("RC[-1]",0),TRUE)';
Note that the formula value has to be a valid Excel formula, not a JavaScript expression as you are using.
Related
I'm trying to retrieve some formatting info from a list of NamedItems. Basically, my spreadsheet has a few named ranges spread across multiple worksheets and I want to get some formatting information like font size for each of these named ranges.
This is what I have so far:
async function getRangesAndJson() {
// 1. Find all prz ranges in file
let myRanges = await Excel.run(async ctx => {
const namedRangesInFile = ctx.workbook.names;
namedRangesInFile.load("items");
await ctx.sync();
return namedRangesInFile.items.filter(range => range.name.startsWith("abc_"));
}).catch(error => {
console.log("error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
// 2. Extract their formatting in JSON, one by one
await Excel.run(async function(ctx) {
for (let index = 0; index < myRanges.length; index++) {
const namedRange = myRanges[index];
const range = namedRange.getRange();
}
await ctx.sync();
}).catch(error => {
console.log("error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
}
I'm not sure how to do this, especially since this operation requires looping a lot.
What would be the best way to achieve this?
In your code, you get the ranges from each named Item, you could use range.format.font to get the font from this range if the font of this range is same.
const range = namedRange.getRange();
range.load(["format/*", "format/fill", "format/borders", "format/font"]);
await ctx.sync();
console.log(range.format.font.name);
if you want to getfont name for each individual cell in this range, you may need to use range.getCellProperties, please refers to this document
BTW, from perf consideration, we suggest not put context.sync in a loop.
I am trying to add cells to the existing table using insert API provided by office js. Microsoft official doc gave sample code to insert in sheet which works fine.
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getItem("Sample");
var range = sheet.getRange("B4:E4");
range.insert(Excel.InsertShiftDirection.down);
return context.sync();
}).catch(errorHandlerFunction);
But i was trying do same with table on the sheet. But it is not working.
table = ctx.workbook.tables.getItem(tableName);
let range = table.getRange();
range.insert(Excel.InsertShiftDirection.down);
Is there a way to achieve this?
You could use Excel.TableRow, the sample code:
Excel.run(function (ctx) {
var tables = ctx.workbook.tables;
var values = [["Sample", "Values", "For", "New", "Row"]];
var row = tables.getItem("Table1").rows.add(null, values);
row.load('index');
return ctx.sync().then(function() {
console.log(row.index);
});
}).catch(function(error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
The document can be found at https://learn.microsoft.com/en-us/javascript/api/excel/excel.tablerowcollection?view=excel-js-preview#add-index--values-
I am looking for a best way to uniquely identify/recognize a selected range within Office.js Excel workbook. So far I am using bindings to set a name for a range i.e. A1:A1. However, it is not clear how to check when user selects the above range if it is a part of the above binding in the workbook.
To set the binding i use the below code:
var myBindings = Office.context.document.bindings;
var myAddress = "Sheet1!A1:A1";
myBindings.addFromNamedItemAsync(myAddress, Office.BindingType.Matrix, { id: "myBind" }, function (asyncResult) {
asyncResult.value.getDataAsync(function (asyncResult2) {
console.log(asyncResult2.value);
});
});
You could use getIntersectionOrNullObject to detect whether your selection is in namedRanges or not. it can return address of the selectedRange in getIntersectionOrNullObject or it will return null object
Here is the sample code
Excel.run(function (ctx) {
const selectedRange = ctx.workbook.getSelectedRange();
var binding = ctx.workbook.bindings.getItemAt(0);
var range = binding.getRange();
var sheetName = "Sheet1";
var rangeAddress = "A1:F8"; // replace to binding range address
// var rangeAddress = range.address;
var range =
ctx.workbook.worksheets.getItem(sheetName).getRange(rangeAddress).getIntersectionOrNullObject(selectedRange);
range.load('address');
console.log("test");
return ctx.sync().then(function () {
console.log("test2");
console.log(range.address);
});
}).catch(function (error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
i'm trying to insert a table inside the content control. Here is my code:
function insertTable() {
Word.run(function (context) {
var range = context.document.getSelection();
var cc = range.insertContentControl();
cc.title = "My Table";
var values = [["Apple", "red", "round"], ["Banana", "yellow", "long"], ["Pear", "green", "oblong"]];
context.load(cc);
return context.sync().then(function () {
var table = cc.insertTable(3, 3, 'Start', values);
})
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
.then(context.sync);
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
}
But i got this error.
Error: {"name":"OfficeExtension.Error","code":"InvalidArgument","message":"InvalidArgument","traceMessages":[],"innerError":null,"debugInfo":{"code":"InvalidArgument","message":"InvalidArgument","errorLocation":""},"stack":"InvalidArgument: InvalidArgument\n at Anonymous function (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:211625)\n at yi (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:249536)\n at st (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:249623)\n at d (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:249443)\n at c (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:248029)"}
I'm using the beta word api:
<script src="https://appsforoffice.microsoft.com/lib/beta/hosted/office.js" type="text/javascript"></script>
Because on api version 1.1 there is not the method insertTable. Any idea why it doesn't work? I've seen on the documentation that this method is available on api version 1.3, are they released?
Thanks
I was stuck with the same problem. It turns out you cannot insert a table in the middle of a paragraph (or in a paragraph that contains something else). When you first add a paragraph, and insert the table in this paragraph you get the desired effect. Please see the code below.
All credits belong to Cindy Meister
function placeTable() {
Word.run(function (context) {
var values = [["Apple"]];
var selectionRange = context.document.getSelection();
var paragraph = selectionRange.insertParagraph("", "Before");
return context.sync()
.then(function () {
var table = paragraph.insertTable(1, 1, "Before", values);
var contentControl = table.insertContentControl();
})
.then(context.sync)
.catch(function (error) {
console.log(error);
});
});
I came here because I got the same error.
However, in my case it was because I provided the insertTable method a column value of less than what the values parameter had.
i.e. my values had three columns and I specified 2 in insertTable.
Hope this helps someone in the future.
I am building a Phonegap application currently targeted primarily for iOS 7+.
I have a local SQLite database that I am copying clean from server. Several of the tables are empty at this time (they will not always be empty). When I use the code below, the result.insertId value is not being populated, but only for the first row inserted into the tables. After the first row all the insertIdvalues are correct.
db.transaction(function (tx1) {
tx1.executeSql(insertSQL1, [arguments],
function (t1, result1) {
if (result1 != null && result1.rowsAffected > 0) {
logMessage("Result1:" + JSON.stringify(result1));
$.each(UserSelectedArrayOfItems, function (index, value) {
db.transaction(function (tx2) {
tx2.executeSql(insertSQL2, [result1.insertId, value],
function (t2, result2) {
if (result2 != null) {
logMessage("Result2: " + JSON.stringify(result2));
}
},
function (t2, error) {
logMessage("There was an error: " + JSON.stringify(error));
});
});
});
<< Do app Navigation if all was ok >>
}
},
function (t, error) {
logMessage("Error: " + JSON.stringify(error));
});
});
While testing, both tables start empty. Zero rows. Both have an ID column with properties: INTEGER PRIMARY KEY. The insert does work and it does get an ID of 1. The ID is correct in the table, but the result.insertId is undefined in the transaction success callback.
The logMessage function writes the strings out to file so I can debug/support the app. Example log messages when inserting 1 row to parent table (always only one to parent) and 2 rows to child table (could be 1 to n rows):
Result1: {"rows":{"length":0},"rowsAffected":1}
Result2: {"rows":{"length":0},"rowsAffected":1}
Result2: {"rows":{"length":0},"rowsAffected":1,"insertId":2}
Has anyone seen this behavior before? I am using the following plugin:
<gap:plugin name="com.millerjames01.sqlite-plugin" version="1.0.1" />
Yes, I found that when you are using a value from a result (or rows) the best practice is to first evaluate the value of object to a new variable, then pass the new variable to another function call and do the work there. As an update to my example:
db.transaction(function (tx1) {
tx1.executeSql(insertSQL1, [arguments],
function (t1, result1) {
if (result1 != null && result1.rowsAffected > 0) {
logMessage("Result1:" + JSON.stringify(result1));
var newID = result1.insertId;
$.each(UserSelectedArrayOfItems, function (index, value) {
DoWork(newID, value);
});
<< Do app Navigation if all was ok >>
}
},
function (t, error) {
logMessage("Error: " + JSON.stringify(error));
});
});
function DoWork(id, value){
db.transaction(function (tx2) {
tx2.executeSql(insertSQL2, [id, value],
function (t2, result2) {
if (result2 != null) {
logMessage("Result2: " + JSON.stringify(result2));
}
},
function (t2, error) {
logMessage("There was an error: " + JSON.stringify(error));
});
});
}
You'll need to add any checks or returns so you can confirm everything went as expected. But that's the overall gist of what fixed it for me.