I am currently trying to implement conditional formatting in Excel using Office JS API 1.6 .I have written the following code to implement text comparison formatting.
function textComparisonFormatting() {
// 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:E8");
var conditionalFormat = range.conditionalFormats.add(Excel.ConditionalFormatType.textComparison);
conditionalFormat.textComparison.load(["rule","format/*","format/fill"]);
//Run the queued commands, and return a promise to indicate task completion
return ctx.sync(conditionalFormat).then(function(conditionalFormat){
conditionalFormat.textComparison.rule.text = "Qtr";
conditionalFormat.textComparison.rule.operator = "BeginsWith";
conditionalFormat.textComparisonformat.fill.color = "red";
});
})
.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));
}
});
}
The code is throwing InvalidObjectPath error while i am trying to set the color. If i try to set the colors inside Excel.Run(), then it won't work as i am unable to access the object properties. Is there any way by which I can resolve these issues?
Some changes you should make to your code:
You don't need to load anything and sync, because you are writing to the properties, not reading them.
There is no ConditionalFormatType.textComparison. You need ConditionalFormatType.containsText.
Operators are camel-cased: beginsWith, not BeginsWith.
There should be a "." between textComparison and format.
This snippet works:
function applyTextFormat() {
Excel.run(function (context) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = sheet.getRange("A2:E8");
var conditionalFormat = range.conditionalFormats
.add(Excel.ConditionalFormatType.containsText);
conditionalFormat.textComparison.format.fill.color = "red";
conditionalFormat.textComparison.rule = { operator: Excel.ConditionalTextOperator.beginsWith, text: "Qtr" };
return context.sync();
});
}
UPDATE: As the OP requested:
The documentation is not yet published on dev.office.com. You need to go to a special branch of the office-js-docs repo on GitHub. Open this page and see all the files of the form conditional*.md:
ExcelJs_OpenSpec/reference/excel
Related
I am working on Microsoft Excel Add-in in react, I want to assign colors to the specific cells based on the value in it i.e, by color code (See Board Color column in image below). So what I did is, get column of the table, and iterate through that column cells and load "value" for each cell. Then iterate on array of those values and try to fill a respective color to each cell.
on iteration, Values are printed in console correctly, but color are not being filled, now it has become headache for me. It is only happening if I assign color in/after "await this.context.sync()". Before that, Color are filled (checked by dummy cells).
Below is the code:
// ExcelAPI.js class
async setBoardColor() {
console.log("setBoardColor()");
try {
let workItemIDColumn = this.workitemTable.columns.getItem("BOARD COLOR").getDataBodyRange().load("values"); // .load(["values", "address"]);
// let workItemIDColumn = await range.load(["values", "address"]);
console.log("Workitem IDs 00 : ", workItemIDColumn);
// console.log("Workitem IDs count : ", workItemIDColumn.values.length);
await this.context.sync().then(() => {
let accountHeaderRange = this.currentWorksheet.getRange("A2:B2");
accountHeaderRange.format.fill.color = "red";
for (var row = 0; row < workItemIDColumn.values.length; row++) {
console.log("in loop : ", row);
console.log("values : " + workItemIDColumn.values[row][0]);
// workItemIDColumn.getRow(row).getRange("A2:B2").format.fill.color = "red";
if (workItemIDColumn.values[row][0] != "") {
console.log("I am in");
workItemIDColumn.getRow(row).getResizedRange(0, -2).format.fill.color = "red"; // workItemIDColumn.values[row][0];
// workItemIDColumn.getRow(row).format.protection.locked = false;
}
}
});
// console.log("Workitem IDs check : ");
} catch (error) {
console.log("setBoardColor() ", error);
}
}
Calling of above method is in main class component.
renderExcelContent = async (workItems) => {
try {
await Excel.run(async (context) => {
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
let excelAPI = new ExcelAPI(context, currentWorksheet);
excelAPI.setBoardColor();
currentWorksheet.getRange("A2:B2").format.protection.locked = false;
currentWorksheet.protection.protect();
// eslint-disable-next-line no-undef
if (Office.context.requirements.isSetSupported("ExcelApi", "1.2")) {
currentWorksheet.getUsedRange().format.autofitColumns();
currentWorksheet.getUsedRange().format.autofitRows();
}
currentWorksheet.activate();
context.runtime.enableEvents = true;
});
} catch (error) {
console.error(error);
}
};
That's just how the Excel JavaScript API works. The things you're doing before calling sync are just interacting with JavaScript objects that aren't actually in the spreadsheet. It's the sync call that writes the changes you've made to the actual worksheet. So if you want to see changes, you have to use sync.
From the documentation (my emphasis):
Excel.RequestContext class
The RequestContext object facilitates requests to the Excel application. Since the Office add-in and the Excel application run in two different processes, the request context is required to get access to the Excel object model from the add-in.
and from sync:
Synchronizes the state between JavaScript proxy objects and the Office document, by executing instructions queued on the request context and retrieving properties of loaded Office objects for use in your code.
(Note that an implicit sync happens after your Excel.run callback returns.)
I have fixed this by "await" before calling setBoardColor() in main class.
await excelAPI.setBoardColor();
Context: I am working on a Addin for Excel using Microsoft's "new" Office Javascript API.
So far, I am able to get the user's selection and a collection of named ranges in the worksheet with the code below:
const selectedRange = context.workbook.getSelectedRange();
const namedRanges = context.workbook.names;
However, what I am trying to achieve is to check if the user's selection selectedRange is a named range in the worksheet and if so get that named range object.
I have not found any "built-in" way to do this so far and the only ideas I have would be a nightmare to implement or result in bad performance.
Is this even possible? What are your ideas?
Thank you for reading!
Thanks for your question.
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 sample code:
Excel.run(function (ctx) {
const selectedRange = ctx.workbook.getSelectedRange();
const namedRanges = ctx.workbook.names;
var sheetName = "Sheet1";
var rangeAddress = "A1:F8"; // replace to named range address
// var rangeAddress = namedRanges.items[0].getRange();
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));
}
});
the document can be found at
https://learn.microsoft.com/en-us/javascript/api/excel/excel.range?view=excel-js-preview#getintersection-anotherrange-
I am trying to develop an Excel JavaScript add-in that processes all selected ranges.
When selecting a large range with data, such as A1:O1000000, desktop Excel hangs when I call the ctx.sync method after getting the ctx.workbook.getSelectedRanges() object.
Also the problem is reproduced in Excel 2016 and Excel 2019 desktop versions.
In Excel Online (web), this code works normally.
My Excel version is 16.0.12430.20172 64-bit
onTestClick(e)
{
console.log("onTestClick");
Excel.run(function (ctx) {
let selectedRanges = ctx.workbook.getSelectedRanges();
selectedRanges.load(["areas", "areaCount", "address", "addressLocal"]);
selectedRanges.areas.load({ $all: false, address: true, addressLocal: true});
return ctx.sync().then(function () {
for (let index = 0; index < selectedRanges.areaCount; index++) {
let area = selectedRanges.areas.items[index];
console.log("area[" + index + "] = " + area.address);
}
}).catch(function (error) {
console.log("onTestClick sync error: " + error);
}).then(()=>{
console.log("onTestClick end");
});
}).catch(function (error) {
console.log("onTestClick Excel.run error: " + error);
});
}
In your script above, load "areas" in selectedRanges is not necessary. You can make some changes on "selectedRanges.load(["areas", "areaCount", "address", "addressLocal"]);" to "selectedRanges.load(["areaCount", "address", "addressLocal"]);" instead. With the change, you can see the result is returned quickly.
The reason is that "areas" is Excel.RangeCollection(https://learn.microsoft.com/en-us/javascript/api/excel/excel.rangecollection?view=excel-js-preview), which returns a collection of rectangular ranges that comprise this RangeAreas object. The return value for Range(A1:O1000000) is too big to be processed.
When calling selectedRanges.load("areas"), it equals to call selectedRanges.areas.load() which is a empty load. An empty-load loads all scalar properties which can have significant performance overhead.
references:
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/performance#load-necessary-properties-only
https://learn.microsoft.com/en-us/office/dev/add-ins/excel/excel-add-ins-advanced-concepts#scalar-and-navigation-properties
I am using PDFJS to get textual data from PDF files, but occasionally encountering the following error:
Error: Invalid XRef table: unexpected first object.
I would prefer that my code just skip over problem files and continue on to the next file in the list. According to PDFJS documentation, setting stopAtErrors to true for the DocumentInitParameters in PDFJS should result in rejection of getTextContent when the associated PDF data cannot be successfully parsed. I am not finding such to be the case: even after setting stopAtErrors to true, I continue to get the above error and the code seems to be "spinning" on the problem file rather than just moving on to the next in the list. It is possible that I haven't properly set stopAtErrors to true as I think I have. A snippet of my code is below to illustrate what I think I've done (code based on this example):
// set up the variables to pass to getDocument, including the pdf file's url:
var obj = {};
obj.url = http://www.whatever.com/thefile.pdf; // the specific url linked to desired pdf file goes here
obj.stopAtErrors = true;
// now have PDF JS read in the file:
PDFJS.getDocument(obj).then(function(pdf) {
var pdfDocument = pdf;
var pagesPromises = [];
for (var i = 0; i < pdf.pdfInfo.numPages; i++) {
(function (pageNumber) {
pagesPromises.push(getPageText(pageNumber, pdfDocument));
}) (i+1);
}
Promise.all(pagesPromises).then(function(pagesText) {
// display text of all the pages in the console
console.log(pagesText);
});
}, function (reason) {
console.log('Error! '+reason);
});
function getPageText(pageNum, PDFDocumentInstance) {
return new Promise(function (resolve, reject) {
PDFDocumentInstance.getPage(pageNum).then(function(pdfPage) {
pdfPage.getTextContent().then(function(textContent) { // should stopAtErrors somehow be passed here to getTextContent instead of to getDocument??
var textItems = textContent.items;
var finalString = '';
for (var i = 0; i < textItems.length; i++) {
var item = textItems[i];
finalString += item.str + " ";
}
resolve(finalString);
});
});
}).catch(function(err) {
console.log('Error! '+err);
});
}
One thing I am wondering is if the stopAtErrors parameter should somehow instead be passed to getTextContent? I have not found any examples illustrating the use of stopAtErrors and the PDFJS documentation does not show a working example, either. Given that I am still at the stage of needing examples to get PDFJS to function, I am at a loss as to how to make PDFJS stop trying to parse a problem PDF file and just move on to the next one.
I'm new to nodejs and jquery, and I'm trying to update one single html object using a script.
I am using a Raspberry pi 2 and a ultrasonic sensor, to measure distance. I want to measure continuous, and update the html document at the same time with the real time values.
When I try to run my code it behaves like a server and not a client. Everything that i console.log() prints in the cmd and not in the browesers' console. When I run my code now i do it with "sudo node surveyor.js", but nothing happens in the html-document. I have linked it properly to the script. I have also tried document.getElementsByTagName("h6").innerHTML = distance.toFixed(2), but the error is "document is not defiend".
Is there any easy way to fix this?
My code this far is:
var statistics = require('math-statistics');
var usonic = require('r-pi-usonic');
var fs = require("fs");
var path = require("path");
var jsdom = require("jsdom");
var htmlSource = fs.readFileSync("../index.html", "utf8");
var init = function(config) {
usonic.init(function (error) {
if (error) {
console.log('error');
} else {
var sensor = usonic.createSensor(config.echoPin, config.triggerPin, config.timeout);
//console.log(config);
var distances;
(function measure() {
if (!distances || distances.length === config.rate) {
if (distances) {
print(distances);
}
distances = [];
}
setTimeout(function() {
distances.push(sensor());
measure();
}, config.delay);
}());
}
});
};
var print = function(distances) {
var distance = statistics.median(distances);
process.stdout.clearLine();
process.stdout.cursorTo(0);
if (distance < 0) {
process.stdout.write('Error: Measurement timeout.\n');
} else {
process.stdout.write('Distance: ' + distance.toFixed(2) + ' cm');
call_jsdom(htmlSource, function (window) {
var $ = window.$;
$("h6").replaceWith(distance.toFixed(2));
console.log(documentToSource(window.document));
});
}
};
function documentToSource(doc) {
// The non-standard window.document.outerHTML also exists,
// but currently does not preserve source code structure as well
// The following two operations are non-standard
return doc.doctype.toString()+doc.innerHTML;
}
function call_jsdom(source, callback) {
jsdom.env(
source,
[ 'jquery-1.7.1.min.js' ],
function(errors, window) {
process.nextTick(
function () {
if (errors) {
throw new Error("There were errors: "+errors);
}
callback(window);
}
);
}
);
}
init({
echoPin: 15, //Echo pin
triggerPin: 14, //Trigger pin
timeout: 1000, //Measurement timeout in µs
delay: 60, //Measurement delay in ms
rate: 5 //Measurements per sample
});
Node.js is a server-side implementation of JavaScript. It's ok to do all the sensors operations and calculations on server-side, but you need some mechanism to provide the results to your clients. If they are going to use your application by using a web browser, you must run a HTTP server, like Express.js, and create a route (something like http://localhost/surveyor or just http://localhost/) that calls a method you have implemented on server-side and do something with the result. One possible way to return this resulting data to the clients is by rendering an HTML page that shows them. For that you should use a Template Engine.
Any DOM manipulation should be done on client-side (you could, for example, include a <script> tag inside your template HTML just to try and understand how it works, but it is not recommended to do this in production environments).
Try searching google for Node.js examples and tutorials and you will get it :)