Building Office Add-Ins Using Officejs
Building Office Add-Ins Using Officejs
js
Michael Zlatkovsky
This book is for sale at http://leanpub.com/buildingofficeaddins
4.2.2
Variables & TypeScript . . . . . . . . . . . . . . . . . . . . 49
4.2.3
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.2.4
Assignments, comparisons, and logical operators . . . . 52
4.2.5
if, for, while . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.2.6
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.2.7
Complex objects & JSON . . . . . . . . . . . . . . . . . . . 58
4.2.8
Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.2.9
Functions & TypeScript . . . . . . . . . . . . . . . . . . . . 62
4.2.10
Scope, closure, and avoiding polluting the global names-
pace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.2.11 Misc. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2.12 jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.3 Promises Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.1 Chaining Promises, the right way . . . . . . . . . . . . . . 71
4.3.2 Creating a new Promise . . . . . . . . . . . . . . . . . . . 73
4.3.3 Promises, try/catch, and async/await . . . . . . . . . . . 82
1
The book and its structure 2
You are currently reading version 1.4 of the book. Release notes for major
updates are posted on http://buildingofficeaddins.com/release-notes
Updates to the book are free for all existing readers (which is what lean-
publishing, and the concept of an evergreen book, is all about!). Simply
go to https://leanpub.com/user_dashboard/library, select the book, and
download your newly-updated copy!
For any issues or topic requests, please file an issue on
http://buildingofficeaddins.com/issues.
• Major re-structuring of the book, splitting out the former chapters 5 &
6 – which were bursting at the seams – into a bunch of smaller chap-
ters. Also streamlined the rest of the book, moving topics that were
less immediately-necessary (e.g., API versioning) further towards the
back of the book, to make it faster to get started.
• As part of the getting-started chapter, added a section for my recom-
mendations of “The optimal dev environment”
• Expanded the section on “Handling errors”.
• Added “Recap: the four basic principles of Office.js” to the chapter on
core Office.js topics.
• Added runnable Script Lab snippets to “Canonical code sample: read-
ing data and performing actions on the document” (including a refac-
tored version that splits the task into multiple subroutines, and a
plain ES5 JavaScript variant).
• Added a topic on the different flavors of Office 2016 / Office 365 – and
the practical implications for developers.
• Added a topic on API Versioning and Requirement Sets.
• Greatly expanded the “TypeScript-based Add-ins” topic, adding in-
structions for the updated Yeoman generator.
• Added a topic for attaching the debugger to Add-ins (breakpoints,
DOM explorer, etc.)
• Added a link to the book’s companion Twitter account.
• Addressed a number of other reader-reported issues.
Having now had experience in both, I think that writing an [evergreen] book
is akin to writing an [evergreen] add-in / website. Try as you may, there will
be bugs; and there will also be not-yet-implemented features, or new ideas
that simply hadn’t occurred before.
To this end, I’d like to provide readers with a way to easily log and track
content issues and topic suggestions. Issues can be:
1.4 Twitter
As reader of the evergreen book, you will receive periodic updates from
LeanPub when I publish a major version (unless you opt out, that is). I expect
to send out such communications once every month or two.
If you’d like to receive more frequent status updates – both about the book,
articles that I put up on my site, interesting StackOverflow questions, or re-
tweets of interesting updates about Office Add-ins that I find on the web – I
encourage you to follow my twitter feed at
https://twitter.com/BuildingAddins
or view the feed embedded on my site, at
http://buildingofficeaddins.com/tweets/
By the same token, if you blog or tweet about the book, I would be much
obliged if you can use the tag #buildingofficeaddins and/or officejs, and also
@mention me: @BuildingAddins.
The book and its structure 8
This book is aimed at the professional developer who is tasked with creating
an Office Add-in (a.k.a an Office Web Add-in, and formerly known as an
App for Office). In particular, it is aimed at the “new” Office 2016+ wave of
Office.js APIs – which, at the time of writing, is supported in Word, Excel, and
OneNote1 .
Office has a rich and wonderful legacy of programmability, with VBA span-
ning the full gamut of developer skill levels – from novice programmers
tweaking simple macros, to professional developers writing complex Office
customization. But for purposes of this book (and due to the more complex
nature of the underlying Office Add-ins platform – for now, anyway), it
is really the professional developer that is the target audience. With that
definition, I mean someone who is well-versed in code, who is eager to
learn something new, and who is unfazed by occasional difficulty (which
you will naturally get, being on the cutting edge of a developing Office Add-
ins platform). A working knowledge of JavaScript is a plus, as is power-user
knowledge of the Office application that you are targeting.
Importantly, this book is not meant as a “standard” API reference manual,
which might walk you through a series of tasks or APIs, focusing on the par-
ticulars. From my perspective, we have dozens of object types, and hundreds
of methods and properties, in each of the Office hosts, all of these are dutifully
documented in our online API reference documentation. To put these in print,
in the form of a static book, would serve very little use.
Instead, this book is about the underlying principles that these APIs, however
diverse, have in common. It is about the conceptual threads that bind the
API surface area with the common runtime that they share. My goal is to
paint the overarching picture of the API model, and to zoom in on details that
transcend beyond any given API object. In this regard, this book is about going
1
As of December 2016, Word, Excel, and OneNote have all adopted the new 2016
wave of Office.js APIs. Outlook – though steadily continuing to expand its APIs – is
continuing to use the “Office 2013” style of APIs even in its Office 2016 applications
(partially because many of its scenarios are more about data consumption, and less
about object-by-object automation, which is where the new model would shine).
PowerPoint and Access have, so far, remained unchanged in their API surface
relative to 2013; and Project, though it has added a number of APIs, is also still using
the “Office 2013” style.
The book and its structure 9
of the new Office.js APIs from the very start, I hope to share some insights &
techniques – and above all, the full end-to-end story – of how to create Add-ins
using the new Office.js.
The book and its structure 12
• Finally, while the book is written by someone who works on the Office
Extensibility Platform team at Microsoft, the information and view-
points presented in this book represent the viewpoints of the author, not
of Microsoft as a company.
The book and its structure 14
1.8 Acknowledgments
First and foremost: the new Office.js paradigm would have been impossible
without the vision and incredible technical skills of Shaofeng Zhu, Principal
Software Engineering Manager on the Office Extensibility Platform team.
Shaofeng paved the way in prototyping the design, in creating a codegen
pipeline that automatically generates the JavaScript and much of the internal
glue, and then in leading the Excel programmability team to ship our first
ExcelApi 1.1 – while simultaneously assisting Word, OneNote, and others to
jump onto the new paradigm. Shaofeng is the unsung hero of the Office.js
APIs for Office 2016 and beyond – so I’d like to take this opportunity to give
credit where credit is due.
I have had a number of people provide feedback in successive edits of this
book. These include:
I would also like to thank Seyit Yilmaz, a Designer on Excel team, who created
the book’s cover image.
Many thanks to all of you!
2. Introduction to Office Add-ins
– What are they, and how are they different from other Office
Extensibility technologies?
– Web Add-ins? What is meant by “web”, and what technologies
are involved?
– Types of Office Web Add-ins (content add-ins, task-pane add-ins,
add-in commands)
• What’s new in the Office 2016 APIs (relative to 2013)?
• What about VBA, VSTO, & COM add-ins?
• “But can Office.js do XYZ?”
• A word on JavaScript and TypeScript
• Office.js: The asynchronous / deferred-execution programming
model
16
Introduction to Office Add-ins 17
on the fence for whether to look into Office Add-ins, I hope that the APIs
described in this book will help change your mind.
Introduction to Office Add-ins 19
For a more detailed comparison of Office Add-ins, VSTO, and VBA, see
https://blogs.msdn.microsoft.com/officeapps/2013/06/18/roadmap-
for-apps-for-office-vsto-and-vba/. The blog is from a few years back,
and was written by yours truly – but it the comparisons it offers are
still relevant today (and note that the new Office 2016 wave of APIs
and the Ribbon & Dialog UX functionality only increases the scope of
what Office Add-ins can do, even if they couldn’t in the Office 2013
incarnation).
Introduction to Office Add-ins 21
I frequently hear the question: “In VBA, I could do XYZ. Can I do it in Office.js?”
The answer, however unsatisfactory it may be, is the usual “it depends”.
From the standpoint of API richness, Office.js is undoubtedly constrained
relative to VBA, simply because it’s an evolving platform – and evolution takes
time. The platform is not there yet with all of the APIs. In fact, it might never
getthere – some APIs might just be deemed too dangerous, or too platform-
specific, to expose. Instead, the Extensibility team has tried to enable key
scenarios, and to provide “families” of related API functionality (i.e., you’ll
see a lot of depth to the Excel Range object, and to Worksheets, and Tables –
but then nothing for Comments or Shapes yet, because it was deemed more
important to provide deep high-quality APIs for a smaller subset of objects,
rather than spreading thin by trying to shallowly deliver on too much).
On the UX front, it’s more of a mix. With Office Add-ins, it’s true that the
developer doesn’t have as much control over the UX (dialogs and ribbon
extensibility have been added in Office 2016, but still not to the same rich
extent as VSTO). On the other hand, I would claim that the UX you can achieve
within a given taskpane is an order of magnitude better than in VBA, and a
fair bit better than a VSTO solution: simply because you can use all of the rich
UX of web design, and easily incorporate libraries and frameworks that offer
fabulous UX. I think this is a real strength of Office Add-ins (as is the appeal
of re-using your HTML/CSS web assets and tools, and developmental skills)
Where Office Add-ins shine is the cross-platform story, the ease of acquisi-
tion from the Office Store, and the ability to deliver instant updates to add-
in code. If you need any of these three, and particularly the cross-platform
bit, Office Add-ins are likely the way to go, even if you might not have all
the API functionality that you wish you had. Also, remember than an API
that is missing today might well be on the roadmap for tomorrow – both
me personally, and the team at large, welcome your feedback with helping
prioritize those (see section “Contributing to the API design process”!) On the
other hand, if you care nothing about cross-platform or the Office Store, and
are deploying a desktop-only solution on IT-issued computer where you can
control anything through Group Policy, VBA or VSTO might well be better
choices for now, as they generally would offer a more familiar programming
model and richer APIs.
Introduction to Office Add-ins 22
In short, do what’s right for your situation and your customers – but do give
Office Add-ins some serious thought, especially if portability, ease of installs
and updates, and the ability to re-use existing web content is important to
you!
Introduction to Office Add-ins 23
Office Add-ins are built using web technologies. This means that, at least for
interacting with the document, you’ll be using JavaScript – lots of it.
JavaScript is a very curious language. It is obviously immensely popular:
in StackOverflow’s developer survey for 20162 , it is far and away the most
popular language for full-stack and front-end developers, and (though by a
smaller margin), is the most popular language even for back-end developers.
And yet, relative to the “standard” languages like C# or Java, the language
is incredibly quirky! No type safety, no out of the box classes, two types of
equality comparisons (== vs. ===), and the list goes on. How do you program
in such an environment?
Coming from the strong and comfortable type safety of C#, VB.NET, and
Java – complete with .NET’s LINQ (Language-Integrated Query Language) and
other runtime goodies – I was frustrated when first encountering JavaScript.
Actually, “frustrated” doesn’t even begin to describe it. I distinctly remember
biking in the rain from work one winter day. I was cold and wet, but one
quizzical thought still managed to surface to my consciousness: “I wonder
what’s more miserable: biking through this sort of weather, or programming
in JavaScript?”
Fortunately, bit by bit, JavaScript grew on me. I actually find it quite delightful
to program in it now, and appreciate the flexibility that if affords. But I still
discover interesting new nuances and surprises (not always pleasant) about
it every day. There is a reason for a book series called “You Don’t Know
JavaScript”3 , aimed at the professional developer!
Still, one thing I could never get used to in JavaScript was the complete lack
of type safety (and reliable type inferencing, for IntelliSense’s sake). Why
couldn’t JavaScript alert me of obvious mistakes, such as using incompatible
types, or misspelling a property name? And why – despite Visual Studio’s best
efforts with JavaScript pseudo-execution at design time – could I not easily4
2
http://stackoverflow.com/research/developer-survey-2016.
3
See https://github.com/getify/You-Dont-Know-JS/blob/master/up%20&
%20going/ch3.md for a good summary of what you may or may not know.
4
Technically-speaking, there are a couple of little-known ways to get the
JavaScript engine to cooperate with you in passing parameter types into functions,
but there is definite effort required. See section “JavaScript IntelliSense”.
Introduction to Office Add-ins 24
some simple code snippets in Visual Studio, with all four of the pain-points
I mentioned earlier. With TypeScript, each of the first three statements are
highlighted as errors, and by specifying my parameter types (e.g., “table:
Excel.Table”), I can keep the full power of IntelliSense even when passing
objects across function boundaries.
But the thing that won me over completely to TypeScript – not for myself, as I
was already a fan, but rather for readers of this book – is TypeScript 2.1, and
the introduction of the async/await keyword. When I started writing the book
in the Fall of 2016, async/await had not yet shipped5 . I would still recommend
TypeScript – for IntelliSense, compile-time safety, template strings, and more
– but I had kept the book firmly rooted in JavaScript, feeling that it is the lingua
franka of the web, and that someone’s choice to opt into TypeScript should be
a purely personal preference. The introduction of async/await has turned the
table, though, to the point that I now firmly believe that new Office 2016 APIs
5
More accurately, the downlevel compilation of async/await to EcmaScript 5
hadn’t yet shipped. Async/await could already be used with ES6 (i.e., for Node
programming), but without compiling down to EcmaScript 5, it was of little use
to websites and add-ins. (For the record, Add-ins on the Desktop presently use
Internet Explorer 11 (or lower, if the computer doesn’t have IE 11) – so whatever
programming you do, it needs to be targeting EcmaScript 5).
Introduction to Office Add-ins 26
and TypeScript were a match made in heaven – and that not using async/await
and the rest of the TypeScript features is lunacy. Once you’ve tried it, I don’t
think you’ll ever look back!
To this end, I will use TypeScript notation liberally through this book, as it
makes the model easier to explain and comprehend. TypeScript is a superset
of JavaScript, so there are only a handful new concepts to learn relative
to JS (and a bunch of JS-specific ones that you’ll no longer need to worry
about!). I will do a very quick crash-course on TypeScript concepts in the
Prerequisites chapter, which should set you up with everything you need
to know for purposes of this book. For those who are determined to avoid
the TypeScript/ES6/async-await bandwagon, please see *“Appendix A: Using
plain JavaScript instead of TypeScript*“, which covers JS-specific techniques
and also shows JS “translations” of some of the key code samples.
For a no-setup (may I say delightful?) way to try out TypeScript with Office.js –
including the beauty of using async/await – you can try it using Script Lab (see
section “Script Lab: an indispensable tool”). Script Lab, a coding & learning
playground for Office.js, accepts both JavaScript and TypeScript input, and
you’ll find that most of its samples make good use of the TypeScript features
listed above.
Once you’ve tried out TypeScript in this setup-less Script Lab environment,
section “*Getting started with building TypeScript-based add-ins** offers
step-by-step guidance for getting Office.js and TypeScript to play together
in a real project. It only takes a couple minutes to set up – so if you are
developing a large and complex application, and/or if you find that the
Introduction to Office Add-ins 27
JavaScript IntelliSense engine in Visual Studio isn’t giving you the type of
support that you’d like, I highly encourage you to give TypeScript a try.
Introduction to Office Add-ins 28
For those who have used VBA before, you will know that VBA code was
always executed in a linear (synchronous) fashion. This is very natural for an
automation task, where you’re essentially manipulating a document through
a series of steps (and where, more often than not, the steps are similar to the
sequential series of steps that a human would do). For example, if you needed
to analyze the current selection in Excel and highlight any values that were
greater than 50, you might write something like this:
When run, such macro would execute line by line, reading cell values and
manipulating them as it went. The macro have complete access to the in-
memory representation of the workbook, and would run almost at the native
Excel level, blocking out any other native Excel operations (or, for that matter,
any user operations, since the VBA code executes on the UI thread).
With the rise of .NET, VSTO – Visual Studio Tools for Office – was introduced.
VSTO still used the same underlying APIs that VBA accessed, and it still ran
those APIs in a synchronous line-by-line fashion. However, in order to isolate
the VSTO add-in from Excel – so that a faulty add-in would not crash Excel,
and so that add-ins could be resilient against each other in a multi-add-in
environment – VSTO had each code solution run within its own fully-isolated
AppDomain. So while .NET code itself ran very fast – faster than VBA by pure
numbers – the Object-Model calls into Excel suddenly incurred a significant
cost of cross-domain marshaling (resulting in a ∼3x slowdown compared to
VBA, in my experience).
Thus, the VBA code above – translated into its VB.NET or C# equivalent –
would continue to work, but it would run far less efficiently, since each sub-
sequent read and write call would have to traverse the process boundary. To
ameliorate that, the VSTO incarnation of the code could be made significantly
faster if all of the read operations were lumped into a single read call at the
very beginning. (The write operations, of setting the background of each cell
Introduction to Office Add-ins 30
VSTO incarnation of the code, with bulk-reading of the selection values (line #4)
Note that the VSTO code, when interacting with the documents, still runs
on (a.k.a., “blocks”) the Excel UI thread, since – due to the way that Excel
(and Word, and others) are architected – all operations that manipulate
the document run on the UI thread. But fortunately for VSTO, the process-
boundary cost – while an order of magnitude higher than that of VBA – is
still relatively small in the grand scheme of things, and the batch-reading
6
Technically not 100% true, since you could create a multi-area range and per-
form a single “write” operation to it – but there is a limitation on how many cells you
can group together, and the performance boost is still not nearly as good as what the
VBA performance would have been. There are some APIs, like reading and writing
to values or formulas, that can accept bulk input/output, but most of the rest – like
formatting – must be done on a range-by-range basis.
Introduction to Office Add-ins 31
the iframe to the parent HTML page, all the way to an Office 365 web server
running in a remote data center. The request would then get executed on the
server, which would dutifully send the response back to the waiting HTML
page, which would pass it on to the iframe, which would finally invoke the
Add-in code. Not surprisingly, such round-trip cost is not cheap.
7
If you’re curious for how the interaction between Office and the embed-
ded Internet Explorer control is done: it is through a window.external API that
IE provides, which acts as the pipe between IE and the process that created it.
The same technique is possible in .NET and WinForms/WPF applications as well.
See https://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.
objectforscripting(v=vs.110).aspx for more info on the latter.
8
Imagine a 50MB Word document, complete a bunch of images. Does it make
sense for the browser to receive all 50MB at once, or could it progressively load only
the pages that it needs in the neighboring vicinity, and only serve up the compressed
and optimized, rather than raw, copies of the images?
9
The actual amount of code that can be run locally (i.e., does not require a
roundtrip to the server) varies greatly depending on the host application. On one
extreme end of the spectrum, Excel Online requires that pretty much all operations
are executed remotely. On the opposite side, OneNote Online has a pretty good local
cache of the document, and needs to call out to the server much less frequently.
Introduction to Office Add-ins 32
To put it into perspective: imagine that the entire roundtrip described above
takes 50 milliseconds, and we are running a hypothetical synchronous and
JavaScript-icized version of the VSTO macro. Imagine that we have one
hundred cells, of which 50 meet the criteria for needing to be highlighted.
This would mean that we need to make one request to clear the formatting
from the selection, another to fetch all of the data, and then 50 requests for
each time that we set on individual cell’s color. This means that the operation
would take (2 + 50) * 50 milliseconds, or just over 2.5 seconds. Perhaps that
doesn’t sound all that terrible… but then again, we were operating on a mere
100 cells! For 1000 cells, we’d be looking at 25 seconds, and for 10,000 cells we
would be at over four minutes. What would the user be doing – other than
sitting back in this chair and sipping coffee – while waiting for the Add-in
operation to complete?!
If synchronous programming was out, the only remaining choice was asyn-
chrony. In the Office 2013 model, this was embodied by a whole bunch of
methods that ended with the word “Async”, such as:
Office.context.document.setSelectedDataAsync(data, callback);
In this Office 2013 design, every operation was a standalone call that
was dispatched to the Office host application. The browser would then wait
to be notified that the operation completed (sometimes merely waiting for
notification, other times waiting for data to be returned back), before calling
the callback function.
Thus, while the Office 2013 APIs solved the Async problem, the solution was
very much a request-based Async solution, akin to server web requests, but
not the sort of automation scenarios that VBA users were accustomed to.
Moreover, the API design itself was limiting, as there were almost no backing
objects to represent the richness of the Office document. The omission was no
accident: a rich object model implies objects that have countless properties
and methods, but making each of them an async call would have been not
only cumbersome to use, but also highly inefficient. The user would still be
waiting their 2.5 seconds or 25 seconds, or 4+ minutes for the operation to
complete, albeit without having their browser window frozen.
The new Office 2016 API model offers a radical departure from the Office
2013 design. The object model – now under the Excel namespace for Excel,
Introduction to Office Add-ins 33
Word for Word, OneNote for OneNote, etc., – is backed by strongly-typed object-
oriented classes, with similar methods and properties to what you’d see in
VBA. Interaction with the properties or methods is also simple and sequential,
similar in spirit to what you’d do in VBA or VSTO code.
Whoa! How is this possible? The catch is that, underneath the covers, setting
properties or methods adds them to a queue of pending changes, but
doesn’t dispatch them until an explicit .sync() request. That is, the
.sync() call is the only asynchrony in the whole system. When this sync()
method is called, any queued-up changes are dispatched to the document,
and any data that was requested to be loaded is received and injected into
the objects that requested it. Take a look at this incarnation of the cell-
highlighting scenario, this time written using the new Office.js paradigm, in
JavaScript:
1 Excel.run(function (context) {
2 var selectionRange = context.workbook.getSelectedRange();
3 selectionRange.format.fill.clear();
4
5 selectionRange.load("values");
6
7 return context.sync()
8 .then(function () {
9 var rowCount = selectionRange.values.length;
10 var columnCount = selectionRange.values[0].length;
11 for (var row = 0; row < rowCount; row++) {
12 for (var column = 0; column < columnCount; column ++) {
13 if (selectionRange.values[row][column] > 50) {
14 selectionRange.getCell(row, column)
15 .format.fill.color = "yellow";
16 }
17 }
18 }
19 })
20 .then(context.sync);
21
22 }).catch(OfficeHelpers.Utilities.log);
Introduction to Office Add-ins 34
As you can see, the code is pretty straightforward to read. Sure, there are the
unfamiliar concepts of load and sync, but if you squint over the load and sync
statements and the Excel.run wrapper (i.e., only look at lines #2-3, and then
#9-18), you still have seemingly-synchronous code with a familiar-looking
object model.
If instead of plain JavaScript you use TypeScript (see A word on JavaScript and
TypeScript), the Office.js code becomes even cleaner.
The same Office.js rendition, but this time making use of **TypeScript 2.1’s ‘async/await‘**
feature
In fact, if you ignore the Excel.run wrapper code (the first and last lines), and
if you squint over the load and sync statements (lines #5-6 and #18), the code
looks reasonably similar to what you’d expect to write in VBA or VSTO!
Still, programming using the new Office.js model – and, for VBA or VSTO
users, adapting to some of the differences – has a definite learning curve. This
Introduction to Office Add-ins 35
book will guide you through getting started, understanding the API basics,
learning the unconventional tricks of debugging Office.js code, and grasping
the key concepts for writing performant and reliable add-ins. It will also give
tips on making your development experience easier, on utilizing some of the
lesser-known functions of Office.js framework, and on writing testable code.
Finally, it will address some frequently asked questions, and give guidance
on broader topics such as how to call external services, how to authenticate
within Office add-ins, and how to publish and license your Add-ins.
Introduction to Office Add-ins 36
Throughout the Office documentation, as well as this book, you may occasion-
ally encounter sentences such as “after calling chart.getImage() and doing
context.sync(), the server will populate the result object with the requested
image”. What is meant by “the server” when you’re issuing a simple API
request to Excel? Does this mean that even when you’re working on the
Desktop, something is being processed by some remote Office 365 endpoint?
Don’t let the wording confuse you. In the context of an Office Add-in, by
the server, what is really meant is the host application – that is, the Office
application that contains your add-in, and which ultimately manipulates
the document on the add-ins behalf. In the case of Office Online, this is in
fact a remote Office 365 server. In case of Office on the Desktop or Mac
or on iOS, however, it’s just the Excel/Word/etc. application. So, in all cases
except Office Online, the operation is executed locally – no remote back-end
server crunches the request. The reason it’s sometimes referred to as the
server is that, from the running JavaScript’s perspective, it’s still a remote and
unknown endpoint, somewhere beyond the JavaScript runtime’s application
boundary… And so it’s still a “server” from that perspective, even if it’s often
just a different process on the same machine.
3. Getting started: Prerequisites &
resources
This chapter will focus on the minimal prerequisites for getting started with
learning the Office.js APIs. The goal of this chapter is to quickly get you started
on being able to try out code samples from this book.
When you are ready to create a new project from scratch (and need guidance
on how to enable TypeScript, for instance), or when you need to learn
about different Office versions, API versioning, and other practical aspects of
building an add-in, please see a later chapter, “The practical aspects of building
an Add-in”.
Chapter structure
• Script Lab: an indispensable tool
• The optimal dev environment
• API documentation resources
37
Getting started: Prerequisites & resources 38
For any and all samples in this book, I highly encourage you to try them out
in Script Lab – a coding playground where you can experiment with Office.js
APIs straight from within Excel, Word, and etc. Script Lab, built itself as an
Office Add-in, is available for free from the Office Store:
https://aka.ms/getscriptlab.
Script Lab in action. Get your free copy of Script Lab at https://aka.ms/getscriptlab
In addition to creating your own snippets in Script Lab, you can also import
someone else’s, or share yours (e.g., to post alongside a question on Stack-
Overflow). To quote the Script Lab README.md file:
Share menu
For purposes of what you need to know to use Script Lab, that’s about it! But
if you’re curious about the project origins, see “Script Lab: the story behind
the project” in Appendix B.
Getting started: Prerequisites & resources 41
You want to make sure you have the subscription version of Office
3. A copy of Script Lab. As mentioned in the previous section, Script Lab will
make it so much easier for you to try out the code exercises, as well as explore
on your own. It comes already pre-configured with TypeScript, IntelliSense
that works right-out-of-the-box, the ability to share and import, and more.
If you haven’t already, get the Script Lab add-in free from the Store: https:
//aka.ms/getscriptlab.
Realistically, that’s all you need to get started with learning the APIs. However,
Getting started: Prerequisites & resources 42
for completeness sake, let me also add a few more tools that you’ll want to
have, once you start developing a real add-in:
4. Visual Studio. Even if you don’t intend to use Visual Studio for creat-
ing/editing the projects (as opposed to using a combo like Node + NPM + VS
Code, which I describe later) – you’ll still need Visual Studio on your machine
in order to debug your code (except for testing in Office Online, where you
can just use the browser’s F12 Developer Tools). See “Debugging: the bare
basics” for more information on either option.
5. [Optional] Node + NPM + Visual Studio Code. Finally, if you are interested
in using modern web tooling, you will need Node and NPM installed (https:
//nodejs.org/download). I would also recommend Visual Studio Code as an
excellent editor that has great IntelliSense (auto-completion) support, a built-
in terminal, a bunch of extensions, and more.
If instead of practicing in Script Lab, you’d prefer to practice within your own
project, please see the “The practical aspects of building an Add-in” for step-
by-step instructions on creating a project. Otherwise, just use Script Lab for
now, and you can switch over later!
Getting started: Prerequisites & resources 43
• Excel: https://dev.office.com/docs/add-ins/excel/excel-add-ins-javascript-
programming-overview
• Word: https://dev.office.com/reference/add-ins/word/word-add-ins-reference-
overview
• OneNote: https://dev.office.com/docs/add-ins/onenote/onenote-add-ins-
programming-overview
• The 2013 wave of “Common” APIs:
Also, for APIs that are not yet implemented or are in beta, you can view the
Open Specs here: http://dev.office.com/reference/add-ins/openspec.
4. JavaScript & Promises primer (as
pertaining to our APIs)
To those first entering JavaScript from the traditional C#/Java/C++ world,
JavaScript may seem like the Wild West (and it is). While it’s beyond the
scope of this book to teach you JavaScript all-up, this chapter captures some
important concepts in JavaScript – and particularly JavaScript’s “Promises”
pattern, and the wonderful way in which TypeScript’s async/await simplifies
it – that will greatly improve the quality and speed at which you write Office
Add-ins.
If you’re a JavaScript/TypeScript pro, you may still find some nuggets of
information useful, but you can also skim/skip ahead if you want to – there
is nothing Office.js-specific here. On the other hand, if you’re not, I highly
recommend reading this chapter and probably returning to it a number of
times later for reference. JavaScript concepts, and especially Promises, are
100% prevalent and essential for understanding Office.js. Jumping straight
ahead to Office.js concepts without understanding the JavaScript & async-
programming basics, is a bit like going off a ski jump before getting your ski
footing on solid ground first1 .
1
Says the person who, in high school, effectively did just that on his first day at a
ski resort… And so can speak with great authority on the subject.
44
JavaScript & Promises primer (as pertaining to our APIs) 45
– Variables
– Variables & TypeScript
– Strings
– Assignments, comparisons, and logical operators
– if, for, while
– Arrays
– Complex objects & JSON
– Functions
– Functions & TypeScript
– Scope, closure, and avoiding polluting the global namespace
– Misc.
– jQuery
• Promises primer
4
http://jsforcats.com/, also available at https://github.com/maxogden/javascript-
for-cats
JavaScript & Promises primer (as pertaining to our APIs) 48
4.2.1 Variables
• Variables in plain ES5 JavaScript are declared using the var keyword:
for example,
var taxRate = 8.49;
• TypeScript, true to its name, let you optionally (and gradually) add type
information to variables, which you do by appending a colon and a type
name after the declaration (you’ll see examples momentarily). Common
types include:
That way, when assigning the value to a variable, the variable will
get implicitly typed. Thus,
let taxRate = retrieveTaxRate();
will now give you the full IntelliSense for a number, rather than an
empty list for an unknown (“any”) type.
Fortunately, TypeScript offers a simple escape hatch from its type sys-
tem: just stick an “<any>” in front of the value you’re setting, and
TypeScript will let the issue go. So, if you want to set a single value to
a property that’s technically a 2D-array, just do something like
range.values = <any> 5;
and both you and the TypeScript compiler will walk away happy.
JavaScript & Promises primer (as pertaining to our APIs) 51
4.2.3 Strings
• If you find yourself in need of both single and double quotes (“it’s
terrible”, said she), you can use the backslash (“\”) symbol to escape the
quote, as in:
console.log('"It\'s terrible", said she');
• Other comparison operators include “!=” for “NOT equal” (with its
stricter best-practice cousin, “!==”), as well as “>”, “>=”, “<”, and “<=”.
• You can use the exclamation mark (!) without the equals sign to negate
an entire statement – for example,
if (!keepGoing) { ... }
Note that “++” is simply shorthand for “i = i + 1”. This means that in the
context of the for loop, this means “increment i by 1 with each iteration
of the loop”. You can specify other increments as well.
...
}
JavaScript & Promises primer (as pertaining to our APIs) 55
4.2.6 Arrays
• To declare an empty array, simply assign “[]” to the array – that is,
var products = [];
• To add new objects onto the array, use the .push method. For example,
products.push('Raspberries');
Other useful methods are .pop (remove the last element of the array)
and .shift (remove the first).
• For a 2D array (which is quite common in Excel APIs, for range values,
formulas, number-formats, etc – and which always have the outer array
elements representing rows, and the inner one columns):
– You can access an individual items via two square brackets one
after another. For example, values[2, 0] will give the element at
relative row index 2 (third row) and column index 0 (first column).
– You can get the count of rows via values.length. To get a count of
columns, grab the first row, and ask it for its count: values[0].length.
}
JavaScript & Promises primer (as pertaining to our APIs) 58
• The objects can be deeply-nested, and can also contain arrays. For
example:
var cellData = {
values: [
["Product", "Price"],
["Kiwi", 3.99],
["Apple", 1.76]
],
format: {
fill: {
color: "green"
}
}
];
Note that the property names could also be quoted: that is, "kiwi" and
kiwi are identical. BUT, if you want a property name that contains
whitespaces or other non-alpha-numeric characters in it, those do need
to be quoted:
var inventory = {
kiwi: { price: 3.99, quantity: 6 },
"granny smith apple": { price: 2.00, quantity: 20 }
};
• To serialize objects into a string (e.g., for storing into a setting), you can
do
JSON.stringify(productInfo);
4.2.8 Functions
• JavaScript functions (though not with TypeScript!) are very loose when
it comes to parameter counts: you can pass in more parameters than the
function accepts, or less, and the runtime won’t complain in either case.
Extra parameters will simply be ignored, and insufficient paramters will
be seen as having an undefined value.
• The order between the function declaration and where it’s used does
not matter. You can invoke a function even if it is technically declared
somewhere below in the source code.
• Functions are “first class citizens” in JavaScript. This means that func-
tions can (and very often are) passed around between methods, espe-
cially as parameters to other functions. For example, you might pass in
a function name to a click handler, as in
$('#setup).click(doSomething);
• As with incoming parameters, a function can also declare its return type.
For example a function that returns an Excel Range object might be
denoted as:
Yes, “fat arrow” is a real term, and you have to admit it’s memorable! Im-
portantly, when working with classes, the fat-arrow syntax also captures
the this variable, making it a “best practice” to use anywhere when
you’re using TypeScript (much as “let” is an improved “var”, even if the
distinction isn’t always necessary). It is also shorter to type.
JavaScript & Promises primer (as pertaining to our APIs) 63
• Related to the above: the “fat arrow” syntax can also provide an implied
return statement, when there is only a single statement being made (and
returned out) in the function body. For example,
When run, the code would always output “5”, because the scope of “i”
isn’t captured by the click handler. My recommendation: use Type-
Script and the let keyword, which avoid this problem altogether.
Or, if you have no choice, use a JS workaround5 .
console.log(MyApp.somethingPublic);
In the absence of TypeScript, you can still create a scope yourself, using
an anonymous self-executing function – which is not necessarily hard,
5
Search for the words “anonymous closure” within https://developer.mozilla.org/
en-US/docs/Web/JavaScript/Closures.
JavaScript & Promises primer (as pertaining to our APIs) 65
6
Close, but simplified relative to what TypeScript itself outputs. See it for yourself
at http://www.typescriptlang.org/play/index.html.
JavaScript & Promises primer (as pertaining to our APIs) 66
4.2.11 Misc.
• To help the JS runtime help you catch errors (e.g., using an undeclared
variable, setting a read-only property, etc.), include a "use strict"
(quoted, just like that) at the top of your file, before anything else. More
info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Strict_mode
• JavaScript statements are (best) separated by semicolons. JS will often
guess correctly even if you omit the semicolon, but things may break
down unexpectedly under certain situations. Best practice is to always
include semicolons at the completion of each statement.
4.2.12 jQuery
jQuery is one of the original libraries to help create and wire-up web UI in
a sane way. jQuery would hide some of the behavioral differences between
browsers, exposing a convenient syntax for manual UI creation
I say manual because there are a variety of other frameworks – Angular or
React, to name some recently-popular ones – that are much more magical, but
also a lot more complex. This is akin to the difference between WinForms and
WPF, for those familiar with creating UI for Windows desktop. jQuery will
allow you to easily wire-up button-click events, and to manually show/hide
elements of the UI – but it won’t do it for you. Angular and friends, on the
other hand, will have you define a model and specialized markup that get
projected into the UI, complete with two-way bindings and all that jazz… but
at the cost of complexity.
Personally, especially for quick prototyping of API functionality, I just use
jQuery. In a few of the prior examples, when you saw the “$” symbol – that
was jQuery. You use $(selector) to retrieve (or create) UI elements, and
then call functions on it to pass in data. The selectors are either class-names
(prefaced with a dot) or ids (prefaced with a “#” sign) that come from the
corresponding HTML.
So, given some basic HTML:
...
<body>
<button id="show-time">Show time</button>
<div id="time"></div>
</body>
...
$('#show-time').click(updateTime);
function updateTime() {
$('#time').text(new Date().toTimeString());
}
• http://blog.webagesolutions.com/archives/138
• https://learn.jquery.com/about-jquery/how-jquery-works/
JavaScript & Promises primer (as pertaining to our APIs) 70
7
See an explanation on https://github.com/kriskowal/q, or just look up this term
on the web. And yes, it is in fact a real term, for which even Wikipedia has an article:
https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming).
JavaScript & Promises primer (as pertaining to our APIs) 71
To the extent that Promises are chained, the term “Breaking the Promise
chain” refers to accidentally letting some of operations run loose. When
the Promise chain is broken, instead of a single strand of operations, you
accidentally end up with multiple parallel execution. This is bad because:
• More often than not, logic that was meant to run in a certain order will
break if the operations start running out of order. This leads to a class
of not-always-reproducible and hard-to-diagnose bugs, known as timing
issues.
• In the case of Office.js APIs and the Excel.run(batch) (and Word.run(batch),
etc.) family of methods, a broken Promise chain can lead to objects being
inadvertently cleaned up8 before you’re done using them… leading to
even harder-to-diagnose timing issues.
• A broken Promise chain can leads to silently-swallowed errors, with the
.catch statement handling only one of the thread chains, but not the
other(s).
The good news is that in the new Office.js9 , the only asynchronous oper-
ation is “context.sync()“. So – other than places in your code where you
explicitly call a web service or do some other async operations – the only
place where you need to watch out for with Office.js is just the context.sync()
method. And moreover, there is exactly one simple rule you need to
follow::
8
See “What does .run do, and why do I need it?”, for details on what gets cleaned
up.
9
In the original 2013 Office.js, Callback were used instead of Promises, so the
Promise discussion is moot for those. Only the Office 2016 wave of document-
manipulation APIs makes use of Promises.
JavaScript & Promises primer (as pertaining to our APIs) 72
The part about await-in (or return-ing) a Promise is critical. Just calling
context.sync() inside of the body of a function, without awaiting/returning
it, will lead to anything from errors being silently swallowed, to everything
going completely awry.
TIP: Not all promises are created equal! Promises are a fairly new
concept (with Chrome and Firefox only adding native support in
late 2015, and with Internet Explorer implementing Promises on
Edge but not IE 11). So especially for Promise polyfills (i.e., libraries
that emulate Promise-like behavior on browsers that don’t natively
support Promises), the Promise behaviors may vary depending on
what polyfill library you are using.
An early adopter of promises was jQuery, with their Deferred object
concept. However, the old jQuery implementation was not up to
the newer A+/Promises spec, and may not play nice (e.g., propagate
errors, etc.) when used in conjunction with native promises or
with the native-like behavior of the OfficeExtension.Promise ob-
ject. Even the jQuery 3.1+ version – while now compliant to the
A+/Promises spec – still differs from standard Promise implementa-
tions (i.e., it does not have a .catch function, instead using its own
.fail). My recommendation for jQuery is to be sure that you’re
using the latest 3.1+ version and also to wrap jQuery Promises in
more standard promises (either manually, or through a library like
Q, available at https://github.com/kriskowal/q; I’ve used Q many
times before, and it’s an excellent Promise library in its own right).
JavaScript & Promises primer (as pertaining to our APIs) 73
The preceding section explained how to properly chain and use Promises
– which is the absolute basics of what you need to know to use Office.js.
However, you may find that sometimes you need to create a brand new
Promise of your own. How and why would you do this?
a
A polyfill is something that emulates native browser behavior on
browsers that doesn’t support a particular bit of functionality (in this
case, Promises). Office.js includes a tweaked version of the ES6-Polyfill
library, taken from https://github.com/jakearchibald/es6-promise. Unlike
the original library, the Office.js version does not polyfill the global window
object (since that may not have been the developer’s intent), and instead
only adds Promise to the OfficeExtension namespace, when needed. If
Promised are already supported natively by the browser (as they might
JavaScript & Promises primer (as pertaining to our APIs) 74
With the technical prerequisites out of the way: Let’s start with why would
you want to create a Promise in the first place. Generally, this will be when
you have an asynchronous method that uses callbacks, and which you’d like
to wrap with a Promise facade, so that it can fit in with the rest of your elegant
workflow.
A very simple HTML example: Let’s say that you want to take a particular
object, and switch its background to reflect a color of the rainbow for a couple
of seconds, before switching to the next one.
In JavaScript, you have setTimeout, which is a function that takes in an action
and invokes it after X many milliseconds. This means that if you really wanted
to, you could do the rainbow-switcher as follows:
A pyramid of ‘setTimeout‘-s
1 function createRainbow() {
2 var $rainbow = $("#rainbow");
3 $rainbow.css("background", "red");
4 setTimeout(function() {
5 $rainbow.css("background", "orange");
6 setTimeout(function() {
7 $rainbow.css("background", "yellow");
8 setTimeout(function() {
9 $rainbow.css("background", "green");
10 setTimeout(function() {
11 $rainbow.css("background", "blue");
12 setTimeout(function() {
13 $rainbow.css("background", "indigo");
14 setTimeout(function() {
15 $rainbow.css("background", "violet");
16 }, 2000);
17 }, 2000);
18 }, 2000);
19 }, 2000);
20 }, 2000);
21 }, 2000);
22 }
Needless to say, this pyramid is less than ideal, as it becomes visually difficult
JavaScript & Promises primer (as pertaining to our APIs) 76
to match the beginning and end of each function. Adding a new step in the
pyramid, somewhere in the middle, also becomes tedious, as you need to
carefully examine the code for the right injection point, and you need to
indent all the rest of the impacted code. Finally, while not relevant for this
example (setTimeout never errors out), error-handling also becomes very
tedious in this callback-style system, as you need to add an error callback
at each layer, and it’s not easy to abort the rest of the processing in case of
an error. What you’d ideally want is to list out the steps in linear function –
which is what Promises are meant for.
Let’s create a pause function, which takes in the number of seconds to pause
for, and returns a Promise that is resolved after the appropriate number of
seconds has elapsed. We will then be able to use it to fix the setTimout pyramid
above.
To create a Promise, all you need to do is invoke the Promise constructor, pass-
ing in a function that represents the action you want taken. From somewhere
within the action, you call resolve(optionalSuccessValue) or reject(error).
And so:
A *pause* Promise
1 function pause(seconds) {
2 return new OfficeExtension.Promise(function(resolve, reject) {
3 setTimeout(function() {
4 resolve();
5 }, seconds * 1000);
6 });
7 }
By the way, in this particular case, the Promise has no way to fail – so we
don’t really need the reject callback. And moreover, because we’re calling
setTimout with an anonymous function whose sole job is to call another
function, we can even simplify the pause function further, if we wanted to:
JavaScript & Promises primer (as pertaining to our APIs) 77
1 function pause(seconds) {
2 return new OfficeExtension.Promise(function(resolve) {
3 setTimeout(resolve, seconds * 1000);
4 });
5 }
Armed with this newly-created function for creating Promises, we can now
solve the rainbow scenario much more satisfactorily:
A Promise-ful rainbow
1 function createRainbow() {
2 var $rainbow = $("#rainbow");
3
4 $rainbow.css("background", "red");
5
6 return pause(2)
7 .then(function() {
8 $rainbow.css("background", "orange");
9 return pause(2);
10 })
11 .then(function() {
12 $rainbow.css("background", "yellow");
13 return pause(2);
14 })
15 ...
16 .then(function() {
17 $rainbow.css("background", "violet");
18 });
19 }
By the way, you’ll notice that there is an anomaly between how the first (red-
background) call is made, relative to the rest. If we want to standardize our
calls to all look the same, we could create an initial Promise, so that even the
red-background call could be part of a .then. To create this starter Promise,
simply call Promise.resolve():
JavaScript & Promises primer (as pertaining to our APIs) 78
1 function createRainbow() {
2 var $rainbow = $("#rainbow");
3
4 return Promise.resolve()
5 .then(function() {
6 $rainbow.css("background", "red");
7 return pause(2);
8 })
9 .then(function() {
10 $rainbow.css("background", "orange");
11 return pause(2);
12 })
13 ...
14 }
Let’s get back to the pause function definition. As we saw, in order to wrap a
callback-style function with a Promise, you create a new Promise, and put the
entirety of the former callback-style code within the Promises’s constructor.
Then, somewhere within the callback code (now residing in the constructor),
whenever the asynchronous operation completes, you would call the resolve
or reject functions. For resolve, you can pass in an optional value, which
the caller of the Promise will be able to read when await-in the Promise
(TypeScript), or inside of the .then function (JavaScript). [Similarly, you can
pass an ‘Error‘ object to the ‘reject‘ function, which can be accessed by the
caller as part of a ‘.catch‘ or a ‘try/catch‘ block]. So for example, if we wanted
a Promise that, after a pause, returns a random number between 0 and 99,
we could do:
1 function getRandomNumberAfterPause(seconds) {
2 return new OfficeExtension.Promise(function(resolve, reject) {
3 setTimeout(function() {
4 resolve(Math.floor(Math.random() * 100));
5 }, seconds * 1000);
6 });
7 }
1 getRandomNumberAfterPause(5 /*seconds*/)
2 .then(function(result) {
3 console.log("After much anticipation, " +
4 "the lucky number is " + result + ".");
5 });
Creating a new Promise for fetching stock-data, which ultimately wraps a jQuery AJAX call
1 function getStockData(stockNamesList) {
2 let quotedCommaSeparatedNames = stockNamesList
3 .map(name => `"${name}"`)
4 .join(",");
5
6 let url = '//query.yahooapis.com/v1/public/yql';
7 let data = 'q=' +
8 encodeURIComponent(
9 'select * from yahoo.finance.quotes ' +
10 'where symbol in (' + quotedCommaSeparatedNames + ')'
11 ) +
12 "&env=store%3A%2F%2Fdatatables.org" +
13 "%2Falltableswithkeys&format=json";
14
15 return new OfficeExtension.Promise(function(resolve, reject) {
16 $.ajax({
17 url: url,
18 dataType: 'json',
19 data: data,
20 timeout: 5000
JavaScript & Promises primer (as pertaining to our APIs) 81
21 })
22 .done(function(result) {
23 console.log("Web request succeeded");
24 var stockDataDictionary = {};
25 var stockDataArray = result.query.results.quote;
26 stockDataArray.forEach(data => {
27 var name = data['Symbol']
28 var price = data['LastTradePriceOnly'];
29 stockDataDictionary[name] = price;
30 });
31
32 resolve(stockDataDictionary);
33 })
34 .fail(function(error) {
35 console.log("Web request failed:");
36 console.log(url);
37
38 reject(new Error(error.statusText));
39 });
40 });
41 }
When you read about using Promises in JavaScript, you will generally see
examples that look as follows:
The above was pure JavaScript. Now, enter TypeScript 2.1, and the async/await
keywords. The usage is very simple: anytime there was a Promise you
wanted to .then on, you now simply await it. Furthermore, in any function
where you have an await, you mark the function with the keyword async
(which, by the way, internally turns the function into a Promise).
Now, let’s see the TypeScript version:
10 // ...
11 // Do your same analysis or processing of the data
12 // ...
13
14 // If you require to do another
15 // asynchronous operation, await it as well.
16
17 } catch (e) {
18 $("#error").text("Error: " + e).show();
19 }
20
21 $("#progress-overlay").hide();
22 }
One gotcha: the async keyword is effectively creating a Promise for you,
which means that you need to have the global window.Promise object defined.
In Script Lab, we include the library core-js by default (option #3), which
provides a Promise polyfill:
If you prefer not to bring in an extra library, you can also rely on the fact that
Office.js (as long as you’re using ExcelApi 1.2+ or WordApi 1.2+) includes a
Promise library. So all you need to do, if you want, is add the following one-
liner somewhere inside of Office.initialized.
JavaScript-only
For folks using JavaScript instead of TypeScript, you may find it
helpful to learn of another technique for Promises: “Passing in
functions to Promise .then-functions“
5. Office.js APIs: Core concepts
Chapter structure
• Canonical code sample: reading data and performing actions on
the document
• Excel.run (Word.run, etc.)
• Proxy objects: the building-blocks of the Office 2016 API model
87
Office.js APIs: Core concepts 88
To set the context for the sort of code you’ll be writing, let’s take a very simple
but canonical example of an automation task. This particular example will
use Excel, but the exact same concepts apply to any of the other applications
(Word, OneNote) that have adopted the new host-specific/Office 2016+ API
model.
Scenario: Imagine I have some data on the population of the top cities in the
United States, taken from “Top 50 Cities in the U.S. by Population and Rank”
at http://www.infoplease.com/ipa/a0763098.html. The data – headers and all,
just like I found it on the website – describes the population over the last 20+
years.
Let’s say the data is imported into Excel, into a table called “PopulationData”.
The table could just as easily have been a named range, or even just a selection
– but having it be a table makes it possible to address columns by name rather
than index. Tables are also very handy for end-users, as they can filter and
sort them very easily. Here is a screenshot of a portion of the table:
Office.js APIs: Core concepts 89
Now, suppose my task is to find the top 10 cities that have experienced the
most growth (in absolute numbers) since 1990. How would I do that?
The code in the next few pages shows how to perform this classic automation
scenario. As you look through the code, if not everything will be immediately
obvious – and it probably won’t be – don’t worry: the details of this code is
what the rest of this chapter (and, to some extent, the book) is all about! But I
think it’s still worth reading through the sample as is for the first time, to gain
a general sense of how such task would be done via Office.js.
Note: In a more real-world scenario, this sample would be broken down into
three or more functions (one to read the data and calculate the top 10 changed
cities; another to write out the table and chart; and a third main function
to orchestrate the other two). For purposes of this sample, though – and in
order to make it easily readable from top to bottom, rather than having the
reader jump back and forth – I will do it in one long function. If you try out
Office.js APIs: Core concepts 90
the sample code in Script Lab, I have both snippet varieties: one identical to
the code below, and another with refactored code. In a later section, “A more
complex context.sync example“, I will show another example of code where
I do split out the tasks into smaller subroutines.
TIP: For those coming from VBA, one thing you’ll immediately see
– and what you’ll need to adjust to – is that everything is zero-
indexed. For example, worksheet.getCell(0, 0), which would be
absolutely incorrect and throw an error in VBA, is the correct way
to retrieve the first cell using the Office.js APIs.
Of course, for user-displayable things like the address of the cell,
it would still report “Sheet1!A1”, since that’s what the user would
have seen. But in terms of programmatic access, everything is zero-
indexed1 .
1
There is hardly a rule without an exception. In Excel in particular, the API
exposes the native Excel function to JavaScript under the workbook.functions names-
pace (i.e., workbook.functions.vlookup(...). Within such functions, it made sense
to keep the indexing consistent with the sort of native Excel formulas that a user
would type (otherwise, what’s the point?!) – and so there, any index is 1-indexed.
For example, if the third parameter to a “vlookup” call, “colIndexNum”, equals to
3, this refers to the third column, not the 4th one. But in terms of the object model,
everywhere else (and in JavaScript itself, of course!), everything is zero-indexed.
Office.js APIs: Core concepts 91
113
114 chart.title.text = "Population Growth between 1990 and 2014";
115
116 // Position the chart to start below the table, occupy
117 // the full table width, and be 15 rows tall
118 let chartPositionStart = fullTableRange
119 .getLastRow().getOffsetRange(2, 0);
120 chart.setPosition(chartPositionStart,
121 chartPositionStart.getOffsetRange(14, 0));
122
123 outputSheet.activate();
124
125 await context.sync();
126
127 }).catch((error) => {
128 console.log(error);
129 // Log additional information, if applicable:
130 if (error instanceof OfficeExtension.Error) {
131 console.log(error.debugInfo);
132 }
133 });
Try it out
If you want to follow along with the code above, just import one of the
following snippet IDs into Script Lab.
1. Same code as above (except a small tweak to the error-handling):
aa81d73587f62f35e46ad6a904bb20df
2. A refactored version, which breaks apart the functions into multiple
subroutines:
fb8913d0a899c88e3ea82773a135dfd0
The Script Lab snippets contain a “setup” button which will populate the
workbook with the necessary data. But if you prefer to download it as a
file, you can find it here:
http://www.buildingofficeaddins.com/samples/population-analysis
Office.js APIs: Core concepts 95
JavaScript-only
For folks using JavaScript instead of TypeScript, you will find it
useful to reference the JavaScript-only version of the canonical
code sample.
For those coming from VBA or VSTO, by far and away the biggest difference
you’ll notice is the need to explicitly call out which properties you want
loaded (nameColumn.load("values")), and the two await context.sync()-s to-
wards the beginning and very end of the operations. But for the rest of the
logic, you have simple sequential code, not unlike VBA. This is the beauty
of the new 2016+ APIs – that they provide you with local proxy objects that
“stand in” for document objects, and allow you to write mostly-synchronous
code (interspersed with the occasional “load” and “sync”). You will read more
about loading and syncing in the forthcoming sections.
If you run the above code, here is what the resulting sheet looks like:
Office.js APIs: Core concepts 96
Let’s start with the very first line of the code: Excel.run. Because the sample
code is based in an Excel scenario, I will use the term Excel.run for the
remainder of this chapter – but the concept is 100% equivalent to Word.run,
OneNote.run, etc.)
1. Use the context to fetch the workbook object (or document in Word, etc.),
and do the other object-navigation from there. A couple examples:
Thus, the typical and simplest use-case for the run method for TypeScript-
based code is:
In the code above, you can see async/await being used within the Excel.run,
but the call to run itself is done using a regular Promise pattern (namely,
without an await in front of it, and with a .catch function at the end). If you
prefer, you can instead wrap the whole thing in a try/catch block, making
sure to await the completion of Excel.run, as follows:
The same ‘Excel.run‘, but this time ‘await‘-ed and wrapped in a ‘try/catch‘ block
1 try {
2 await Excel.run(async function(context) {
3 // ...
4 await context.sync();
5 });
6 } catch (error) {
7 // ...
8 }
It doesn’t matter which approach you choose: the only important thing is
to ensure that you don’t let the asynchronicity spin out of control – so you
2
There is a very particular use-case where, for performance reasons, you may
need to avoid Excel.run and use a raw request context instead. In the entire time
that I’ve been answering StackOverflow questions, I’ve seen exactly one real-world
example that would have benefited from this. I will describe this use-case later in
the book, in “When to use a raw RequestContext in place of an Excel.run”, but I don’t
think you should worry about this too much for now. If you are in fact experiencing
performance issues, there are a number of other more likely performance offenders,
which I describe later on in the book, in “Summary: writing efficient code”.
Office.js APIs: Core concepts 99
must either await the Excel.run, or use .then-s and .catch-es on it. The part
about catching errors is particularly important, as there are a wide array
of circumstances under which API calls will fail (especially while you’re
still developing the application). Thus, while I’m a big fan of async/await
and 100% recommend using it inside of Excel.run, my worry with using it
outside is that it’s really easy to forget the await in front of Excel.run. If you
do, you will be left mistakenly confident that you’re protected by the catch
statement, whereas in actuality the catch does nothing if you forgot to await
the asynchronous portion3 .
Whichever way you go, for purposes of getting started4 , that is really all that
you need to know about Excel.run. So, with this Excel.run preamble out of the
way, the subsequent sections will focus on what you can do within the batch
function, after the process has been jump-started by the Excel.run.
3
I actually had this happen to me right as I was re-writing the “canonical sample”
of this chapter, TypeScript-ifying my previously-JavaScript-based content. It was
clear that the API was stopping midway due to some issue, and yet there was no
indication of the error! Once I noticed the omission, I promptly switched my still-
being-TypeScript-ified guidance to recommend async/await inside the run, but not
outside of it.
4
The Excel.run function also has a number of other overloads, which are covered
in detail in “Excel.run ( Word.run, etc.) advanced topics”.
Office.js APIs: Core concepts 100
At the fundamental level, the Office 2016+ API model consists of local JavaScript
proxy objects, which are the local placeholders for real objects in the docu-
ment. You get a proxy object by calling some property or method (or a chain
of properties or methods) that originate somewhere off of the request context.
For example, if you have an Excel object called workbook, you can get cells
A1:B2 on Sheet1 by calling
To apply values to the object, you would simply set the corresponding prop-
erties. For example:
The same applies to calling a method – you simply invoke it. For example,
myRange.clear();
Importantly, what happens beneath the covers is that the object – or, more
accurately, the request context from which the object derives – accumulates
the changes locally, much like a changelist in a version-control system. Noth-
ing is “committed” back into the document until you recite the magical
incantation:
context.sync();
Office.js APIs: Core concepts 101
Let’s pause for a moment. If all of these JavaScript objects are simply proxy
objects, and all you’re doing is queuing up commands, how long can you
keep going? It turns out that as long as you don’t need any information
back from the document (i.e., you’re not reading back some values and then
multiplying or formatting or doing whatever else with them), you can create
an arbitrarily-long queue of commands. In the population-data example,
after reading back the initial batch of data, I issued a bunch of commands
all at once: creating a new sheet, setting some values on it, overlaying it
with a table and adding a bunch of rows, formatting the table, adding and
positioning a chart, and finally setting focus on the newly-created output
sheet.
On the JavaScript side, whenever I called a property that involved primitive
types – so, for example, setting sheetHeader.values – the action would get
noted and added to the internal queue, and then the JavaScript operations
would proceed as is. Likewise, whenever I called a void returning method
– such as sheetHeader.merge() – this too would get internally noted and
added to the queue. If I called a property or method that returns an API
object, though – for example, context.workbook.worksheets.add(...), or out-
putSheet.getRange("B2:D2") – Office.js would go and create a new on-the-
fly proxy object and return it to me, noting internally how this object was
derived.
In all those cases, the overarching paradigm is that Office.js tries to create the
illusion that the operations are synchronous, even though in reality it simply
added them to an internal queue, and waits until they can be “flushed” as
part of a context.sync().
Office.js APIs: Core concepts 102
The previous section described what happens on the JS layer for outgoing
commands. But what happens on the receiving end? On the host application’s
side (a.k.a, “the server”), the long chain of commands is executed one-by-one,
resolving any proxy objects as the need arises (“supply on demand”?). So for
example, when a call comes in to add a sheet, the resulting object is internally
stored for the duration of the batch; and then, when this object gets used in
a subsequent line for retrieving a range, this range is likewise created (and
internally identified as being created off of a particular parent object, using
method such-and-such with parameters such-and-such). When a subsequent
call comes in to set values of the range, this call – which in turn references
the object that it’s operating on – is identified to mean the range that was just
recently created. And so forth.
Office.js APIs: Core concepts 103
var newSheet =
context.workbook.worksheets.add("NewSheet");
// ...
context.sync().catch(...);
If this all seems magical – well, to some extent it down right is! But it is
important to remember that until you call context.sync(), you’re effectively
building castles in the sky. And, an interesting corollary to this is that even
erroneous code (such as accessing a non-existing item) won’t throw until it’s
part of the context.sync() execution – up to that point, the changes are merely
queued up. So for example, you can easily write the following code:
Office.js APIs: Core concepts 104
1 context.workbook.worksheets.getItem
2 ("SheetThatDoesNotExist").getRange("A1:B2").clear();
3 console.log("I'm past the call");
4 context.workbook.getSelectedRange().format.fill.color = "yellow";
5
6 context.sync().then(...).catch(...);
When you run the above code, execution at the JavaScript layer will happily
keep going past the SheetThatDoesNotExist line to the next JavaScript call:
console.log("I'm past the call)". And in fact, it will keep on going all the
way through till context.sync().
This is probably not what you’d expect – after all, shouldn’t the invalid-sheet-
fetching throw an error? In a synchronous VBA/VSTO-like world, it abso-
lutely would. But remember, in this proxy-object/batched/deferred-execution
world of Office.js, all you’re doing is building up an array of commands, but
not yet executing them! As discussed in “Why is Office.js async?”, querying
the document every time to perform a document-object-model request would
be impractical. And so, without that direct feedback, the JavaScript layer
has no way of knowing that SheetThatDoesNotExist does not exist in the
workbook. Only when the JS layer is waiting on the asynchronous completion
of context.sync() – and when the host application is meanwhile processing
the queue of commands – does the error become apparent. At that point, the
execution halts on the host applications’ side, dropping any other calls that
were queued up in the batch (in this case, setting the selected range to yellow).
The host then signals back to the JavaScript engine to return a failure in the
form of a rejected promise. But importantly, any non-API-object JavaScript
calls that preceded context.sync() will have already run!
This is the trickiest part of the new Office.js’s async model, and the most
common pitfall (especially if you accidentally let a context.sync() run loose
without awaiting it, thereby executing a bunch of JavaScript before the
document object-model calls came back). So remember: it’s all proxy objects
– and nothing happens until you call context.sync()"!
Office.js APIs: Core concepts 105
The preceding section only talked about setting properties or calling methods
on document objects. What about when you need to read data back from the
document?
For reading back document data, there is a special command on each ob-
ject, object.load(properties). An identically-functioning method can also be
found on the context object: context.load(object, properties). The two are
100% equivalent (in fact, object.load calls context.load internally), so which
one you use is purely stylistic preference. I generally prefer object.load, just
because it feels like I’m dealing with the object directly, rather than going
through some context middleman/overlord. But again, it’s purely stylistic.
The load command is queued up just like any of the others, but at the
completion of a context.sync() – which will be covered in greater detail in
a later section – an internal post-processing step will automatically populate
the object’s properties with the requested data. The property names that you
pass in into the object.load(properties) call are the very properties that –
after a sync – you will be able to access directly off of the object.
For example, if you look at the IntelliSense for the Excel Range object, you will
see that it contains properties like range.values and range.numberFormat. If
you need these values and number formats in order to execute your function
(maybe to display them to the user, or to calculate the minimum/maximum,
or to copy them to a different section of the workbook, or to use them to make
a web service call, etc.), you would simply list them as a comma-separated list
in the .load function, as show in the example below:
11
12 }).catch(...);
TIP: In the above code, you see the use of JSON.stringify. For
those relatively new to JavaScript: JSON.stringify, used above, is
a very convenient way of viewing an object that might other-
wise show up as [object Object] (typically a complex type or a
class). The JSON.stringify also helps for types that might otherwise
show up incorrectly, like a nested array (for example, the array
[[1,2],[3,4]] shows up as the string 1,2,3,4 if you call .toString()
on it, instead of its true representation of [[1,2],[3,4]]).
Importantly, just as in the code above, the reason to call object.load is that
you intend to read back the values in the code following the context.sync(),
and that your operation can’t continue without having that information. This
brings us to arguably the most important rule in all of Office.js:
By the way, the return value of object.load(...) is the object itself. This is
done purely for convenience, saving a line or two of vertical space. That is,
Throughout the preceding sections, you will have seen the term – and seen
example usage of – context.sync(). The purpose of context.sync() is for the
developer to declare that he/she is done with either the entire batch or a
portion of it. An invocation of context.sync() returns a Promise object, which
can then be await-ed or .then-ed with follow-up operations (which in turn
might also call to context.sync() to signal the completion of their portion of
the batch). Importantly, chaining Promises together returns a meta-Promise,
which resolves only when all of the child Promises have finished chaining.
This Promise is what must be returned out of the batch function, so that .run-
method knows that the OM operations have completed5 .
up with a scenario that did need more than two sync calls. The trick for
minimizing sync calls is to arrange the application logic in such a way that
you’re initially scraping the document for whatever information you need
(and queuing it all up for loading), and then following up with a bunch
of operations that modify the document (based on the previously-loaded
data). You’ve seen several examples of this already: one in the intro chapter,
when describing why Office.js is async; and more recently in the “canonical
sample” section at the beginning of this chapter. For the latter, note that the
scenario itself was reasonably complex: reading document data, processing
it to determine which city has experienced the highest growth, and then
creating a formatted table and chart out of that data. However, given the
“time-travel” superpowers of proxy objects, you can still accomplish this task
as one group of read operations, followed by a group of write operations.
Still, there are some scenarios where multiple loads may be required. And in
fact, there may be legitimate scenarios where even doing an extra sync is the
right thing to do – if it saves on loading a bunch of unneeded data. You will
see an example of this later in the chapter.
If and when an error does occur, the most important thing is to be aware that
this has happened. Silent errors, esp. in developing with Office.js, can easily
happen if you forget to add appropriate error handling.
In the example in at the very start of this chapter (see “Canonical code
sample”) – and really, anywhere else throughout the book and online samples
– one thing to note is the presence of a .catch statement. The .catch traps any
errors that could have occurred anywhere within the “.run” operation. As
explained in “Promises, try/catch, and async/await”, a .catch is the Promise
equivalent of a try/catch block. With TypeScript, you can use either, but
I personally find it easier and more concise to use .catch-es outside of
the Excel.run (again, see the referenced section for an explanation of my
rationale for this).
When writing quick prototype code, it’s easy to fall into the trap of skipping
error handling. Don’t. It’s literally less than 50 extra characters.
.catch(function(e) {
console.log(e);
});
In fact, in its most basic form6 , you can make it just 20 extra characters:
.catch(console.log);
Trust me, I’ve seen it over and over again: the five extra seconds it takes to
type a catch function/statement, relative to the time spent on head-scratching
6
Why does this work? Remember that functions are first-class citizens in
JavaScript. The .catch (or .then, for that matter) expects to be given a function that
it can call, passing in the error object as a parameter. You can define the function as
an anonymous lambda function, as in the previous example, but if all you’re doing is
turning around and calling another function, there’s hardly a point to wrapping the
call. You can instead just pass in the function itself to the .catch.
Office.js APIs: Core concepts 111
and pondering why nothing seems to be happening, is well worth the invest-
ment.
If the error comes from the Office.js APIs (as opposed to a JavaScript runtime
error, or a failed ajax request, etc.), it will contain an error code, message,
and often a useful .debugInfo property. Let me describe each of them in
detail:
• The default toString representation of the error will give you “<Error-
Code>: <Message>”. Again, this is meant at being reasonably human-
friendly for errors that might get bubbled up to the user via some
notification UI.
to the user, typically via a notification toast or dialog. You would then
do the additional logging, making use of the debugInfo, as follows:
1 console.log(error);
2 // Log additional information, if applicable:
3 if (error instanceof OfficeExtension.Error) {
4 console.log(error.debugInfo);
5 }
Once you have your error-handling function, you would use it as fol-
lows:
Using the custom error handler via a ‘.catch‘ statement
1 Word.run(async (context) => {
2 // ...
3 await context.sync();
4 }).catch(customErrorHandler);
… or …
Using the custom error handler via a ‘try/catch‘ and an ‘await‘
1 try {
2 await Word.run(async (context) => {
3 // ...
4 await context.sync();
5 });
6 }
7 catch (e) {
8 customErrorHandler(e);
9 }
• Finally, the error object may include traceMessages, which are custom
messages that you can log yourself. See “Finding the point of failure:
Trace Messages” for more information on using trace messages.
Office.js APIs: Core concepts 113
In most samples in this book – and in the official documentation – you will
see that the .catch method uses console.log.
While this is a fine thing to do, remember that the user won’t see these
error notifications (which, for a JSON-ified debugInfo, may very well be a
good thing) – but it also means that the user might not realize that the
function has errored out and stopped processing. By the same token, when
running/debugging your own code, the errors may also appear silent to you,
unless you’re looking in the output window!
This is yet another reason to create your own error-handling function, and
have it display the errors on the UI. I recommend always including the error-
handling function, irrespective of how simple the code might be, as there
might still be some corner-cases or issues beyond your control, where the
user should have at least some indication of the failure.
Note that in the built-in Office Add-in project template that comes with
Visual Studio, you can use “app.showNotification(header, text)” as a simple
way of displaying these errors in a taskpane or content add-in. Alterna-
tively, if you are using Office JS Helpers (more on that below), you can use
“OfficeHelpers.UI.notify(title, text)”. For functions that run headless as a
result of a ribbon-button click (known more formally as “Add-in commands”),
you will want to pop out a standalone dialog. See section “Pop-up dialogs” for
instructions.
Office.js APIs: Core concepts 115
Regarding the OfficeHelpers functionality, the code is available from the “Of-
fice JavaScript API Helpers” project (see https://github.com/OfficeDev/office-
js-helpers, or via the office-js-helpers NPM package).
Putting this advice together leads to the following code (which coincidentally
is just the “Basic API Call” sample in Script Lab):
13 }
14
15 /** Default helper for invoking an action and handling errors. */
16 async function tryCatch(callback) {
17 try {
18 await callback();
19 }
20 catch (error) {
21 OfficeHelpers.UI.notify(error);
22 OfficeHelpers.Utilities.log(error);
23 }
24 }
Office.js APIs: Core concepts 117
While Office.js may initially seem like unfamiliar terriotory, I firmly believe
that its basic principles are very much teachable. With a bit of patience, the
proxy-object and load/sync concepts will become second-nature. In fact, the
entirety of the core Office.js topics can be summarized in just a few bullet
points!
Here’s a code block that demonstrates the entirety of these four principles:
Try it out
Just import the following Snippet ID into Script Lab:
39c81dabec330a70e72e194b06c36bce
6. Implementation details, if you
want to know how it really works
In writing this book and receiving feedback from early readers, I’ve heard
several requests for a more thorough explanation of what happens under the
covers with all this proxy-object / syncing business.
So, if you’re the sort of person who likes to see the implementation details
in order to better grasp a technology – and if curiosity about the internal
workings is going to distract you until you know the answer – let’s pause here
and let you read on for a moment.
If you’re not, feel free to skip to the next chapter. You can always return here
later, after you’ve seen more of the APIs in action, if that’s when your curiosity
awakens.
Chapter structure
• The Request Context queue
• The host application’s response
• Back on the proxy object’s territory
• A special (but common) case: objects without IDs
119
Implementation details, if you want to know how it really works 120
At the heart of the new wave of Office 2016 APIs is a Request Context –
which is the object you receive as a parameter to the batch function, inside
of an Excel.run. Fundamentally, you can think of a Request Context object
as a central repository that accumulates any changes you’d like to do to the
document. I say “repository”, because the Request Context could indeed be
likened to a version-control system, where all you send is the diff -s between
the local state and the remote state1 .
The Request Context holds two arrays that allow it do it its work. One is for
object paths: descriptions of how to derive one object from another (i.e., “call
method getRow with parameter value 2 on <insert-some-preceding-object-path>
in order to derive this object”). The other is for actions (i.e., set the property
named “color” to a value of “purple” on the object described by object path
#xyz). For those who are familiar with the “Command” Design Pattern, this
notion of carrying around objects that represent the recipe for a particular
action should sound quite familiar.
On the Request Context is a single root object that connects it to the underlying
object model. For Excel, this object is a workbook; for Word, it is document.
From there, you can derive new objects by calling methods on that root
proxy object, or on any of its descendants. For example, to get a worksheet
named “Report”, you would ask the workbook object for its worksheets property
(which returns a proxy object corresponding to the worksheets collection in
the document), and then use worksheets to call a getItem("Report") method in
order to get a proxy object corresponding to the desired “Report” worksheet.
Each of these objects carries a link to its original Request Context, which in
turn keeps track of each object’s path info: namely, who was the parent of
this new object, and what were the circumstances under which it got created
(was it a property or a method-call? were there any parameters passed in?).
Whenever a method or property gets called on a proxy object, the call is
1
Git is a particularly well-suited version-control analogy, as local changes are so
perfectly isolated from the repository: until you do a git push of your local state, the
repository has no knowledge whatsoever about the changes being made! The Request
Context and proxy objects of the new Office.js model are very much the same: they
are completely unknown to the document until the developer issues a context.sync()
command!
Implementation details, if you want to know how it really works 121
Now let’s analyze it with a fine-toothed comb. With each API call, I will keep a
running tally of the object path and their actions (expressed in a friend-liefied
and shortened notation, but following closely to what happens internally).
To start, line #1 – Excel.run(async (context) => { – uses Excel.run to create
a Request Context object. The .run invocation does a number of other things
too (See “Excel.run ( Word.run, etc.) advanced topics”), but let’s leave it be for
the time being. The important thing is that it gives us a brand new context
object, on which there is already a pre-initialized a workbook object (which
we’ll use momentarily).
2
The property-setting is intercepted by subscribing to the setter for the
property – see TypeScript getters and setters, which ultimately translate to
object.defineProperty in the EcmaScript 5 (ES5) incarnation of JavaScript.
Implementation details, if you want to know how it really works 122
objectPaths:
1 => global object (workbook)
actions: <none>,
objectPaths:
P1 => global object (workbook)
P2 => (range)
parent: "P1", type: "method",
name: "getSelectedRange", args: <none>
actions:
A1 => action: "init", object: "P2" (range)
objectPaths:
P1 => global object (workbook)
P2 => (range)
parent: "P1", type: "method",
name: "getSelectedRange", args: <none>
actions:
A1 => action: "init", object: "P2" (range)
A2 => action: "method", object: "P2" (range)
name: "clear", args: <none>
Implementation details, if you want to know how it really works 123
objectPaths:
P1 => global object (workbook)
P2 => (range)
parent: "P1", type: "method",
name: "getSelectedRange", args: <none>
P3 => (thirdRow)
parent: "P2", type: "method",
name: "getRow", args: [2]
actions:
A1 => action: "init", object: "P2" (range)
A2 => action: "method", object: "P2" (range)
name: "clear", args: <none>
A3 => action: "init", object: "P3" (thirdRow)
objectPaths:
P1 => global object (workbook)
P2 => (range)
parent: "P1", type: "method",
name: "getSelectedRange", args: <none>
P3 => (thirdRow)
parent: "P2", type: "method",
name: "getRow", args: [2]
P4 => (format)
parent: "P3", type: "property",
Implementation details, if you want to know how it really works 124
name: "format"
P5 => (fill)
parent: "P4", type: "property",
name: "fill"
actions:
A1 => action: "init", object: "P2" (range)
A2 => action: "method", object: "P2" (range)
name: "clear", args: <none>
A3 => action: "init", object: "P3" (thirdRow)
A4 => action: "init", object: "P4" (format)
A5 => action: "init", object: "P5" (fill)
A6 => action: "setter", object: "P5" (fill),
name: "color", value: "purple"
And finally, on line #7 (line #6 was a blank one), we get to the magic await
context.sync() incantation. This command tells the Request Context to pack
up all of the relevant information (namely, pending actions, and any associ-
ated object path info-s), and send it to the host application for processing.
On the receiving ends of the host application, the host unpacks the actions
and begins iterating through them one-by-one. It keeps a working dictionary
of the objects that got derived during this particular sync session – such that,
having retrieved the range corresponding to thirdRow once, it will not need
to re-evaluate it again. This is done not only for efficiency, but also to prevent
mistakes: you wouldn’t want to re-fetch the row at relative index 2 if another
few rows got added between it and the first row; nor would you want to re-
fetch the selection every time, since it may well have shifted (e.g., during the
adding and activating of a new worksheet), and yet semantically the range
should be imprinted with the original reference. Finally, if you have an object
derived from calling the add method on the worksheet-collection object, you
definitely wouldn’t want to re-derive the object – and, as a side effect, add a
new sheet – every time that the object was accessed!
If at any point in the chain something goes wrong, the rest of the batch
gets aborted. Going with the previous example, if there is no third row in
the selection (i.e., it’s a 2x2 cell selection), the remaining commands would
get ignored (which is probably what you’d expect, anyway). Importantly,
though, there is no atomicity to the Excel.run or the sync: any actions that
Implementation details, if you want to know how it really works 125
have already been done will stay done. In the case of this example, the
document might be left in a dirtied state, where the clearing of the selection
has already happened, but the formatting of the third row has not been done
yet. While not ideal, this is no different from VBA or VSTO with regards to
Office automation; it is simply too difficult to roll back, especially given any
user or collaborator actions that may have happened in the meantime3 . See
the section “Handling errors“ for my recommendations on how to minimize
the risk of leaving a user in an undesired state.
Let’s assume that the sync did succeed: that every necessary object (the orig-
inal selection, its third row, the format, the fill) all were created successfully,
and that both document-impacting actions were also able to commit to the
document. What happens next?
As mentioned earlier, the host keeps a running dictionary of the object that
it’s been working with. But this running dictionary is only for the duration of
the particular sync: not for the lifetime of the application. To keep and track
the objects indefinitely would be a huge performance hit.
Now, let’s take the case where an object path is the “add” action on a
worksheet collection. During the processing of the sync, the method would
only have been executed once (with the appropriate side-effect of creating
the worksheet), and the resulting sheet would be cached. This is great for the
current sync, but what if the developer wants to access the sheet again at a
later sync? This is where the instantiation actions mentioned earlier come in.
For each action, the host application may optionally send a response. For
actions like clearing a range or setting a fill color, there is nothing to respond
with (the fact that the operation succeeded is obvious through the fact that the
queue kept executing to completion). But for instantiation actions, the host
may send a response to tell JavaScript to re-map its object path to something
less volatile. Thus, while the original path for a newly-created sheet may
3
While atomicity is possible in well-structured, GUIDs-everywhere databases, it’s
much harder to imagine it in the loose structure of an Excel or Word document.
I should also note that in Excel (but not Word), you will see that any document-
impacting (write) operations will cause the undo stack to be blown away – again,
far from ideal, but no different than VBA’s or VSTO’s behavior today for Excel.
Implementation details, if you want to know how it really works 126
have been “execute method ‘ add on object xyz” (where xyz is the worksheet
collection), the response might indicate “*from here on out, refer to the sheet
as being a “getItem” invocation with parameter “123456789” on that same
xyz object”. That is, in creating the object and executing the instantiation
action, the host can figure out if there is a more permanent ID it can give
back to JavaScript for future references to this object. (A less drastic example:
fetching a sheet by name is somewhat risky, in that names can change, both
via user interaction and programmatically; but if the host can re-map the
path to a permanent worksheet ID, any future invocations on the object are
guaranteed to continue to refer to the same sheet, regardless of its name).
But there is another, even more important use for responses from the host.
Suppose, on the JavaScript side, you have a call to range.load("formulas").
In terms of actions, this gets represented as a query action on the object,
with a parameter whose value is “formulas”. To this action, the host will
respond by fetching the appropriate object (which is already in its dictionary,
thanks to the instantiation action), querying it for the required properties,
and returning the requested information.
Back in JavaScript, the sync is patiently waiting for a response from the host
application. And, hopefully, the developer’s code is also patiently waiting,
by either using an await or subscribing to the .then function-call of the sync
Promise.
When the response does come back, there is a bit of internal processing
before the execution gets back to the developer’s code. For example, any of
the path-remappings, described in the preceding section, take effect. There
is also some internal processing (e.g., invalidating the paths of objects that
were valid during the previous sync batch, but cannot be used again – I’ll
explain more soon). And, importantly, the results of any query actions take
effect, taking the loaded values and putting them back on the corresponding
objects and properties. This ensures that, following the sync if the developer’s
code now references range.values for a Range whose values have been
loaded, he/she will get the last known snapshot of the values (as opposed to
a PropertyNotLoaded error, mentioned earlier in “Loading properties: the bare
basics”).
Implementation details, if you want to know how it really works 127
With the post-processing done, the request context is now ready to be re-
used. Its actions array was reset to a blank slate at the very beginning of the
sync; and conversely, the object paths array (which is never emptied during
the lifetime of the particular request context, as later actions are bound to
re-use some of the existing paths) has had any of the object paths tweaked,
based on responses from the host and post-processing. And so a new batch of
operations can begin, queuing up until the next await context.sync().
When working with objects like worksheets (Excel) or content controls (Word),
the host application’s job is quite easy: in both cases, there is a permanent
ID attached to each of those objects, so no matter how the object was created
(getActiveWorksheet(), or getItem, or whatever other invocation), the host can
always use the instantiation action to re-map the path back to a permanent4
ID. Which means that, as a developer, having created the object once at some
point, you can continue to use it in the next sync, or even longer thereafter5 .
No surprises there.
But what about objects that don’t have IDs; and that, by definition, have an
infinite number of permutations about them? Both Excel ranges (a particular
grouping of cells) and Word ranges (some text starting at one location and
ending in another) are not at all easy to get a concrete reference to: the
address/index at which they are might shift, and the ranges might also grow
and expand if additional cells or characters are added within them. The same
4
A “permanent ID”, in this context, means an ID that won’t change for the session
of the document being currently opened. The IDs are not necessarily guaranteed
to remain the same during a re-opening of the document, so you should check
the documentation carefully for whether an ID can be used across document-open
sessions. But for purposes of the API processing and the JavaScript (which also is only
open for the duration of the document, or shorter), the ID is permanent enough.
5
Such objects can even be used outside of the current Excel.run, in a proce-
dure some time later (e.g., as part of a subsequent user button-click). See http:
//stackoverflow.com/a/37290133/678505, and note that an even better approach is
forthcoming.
Implementation details, if you want to know how it really works 128
like to be tracked. On the host side, this action would be interpreted to create a
permanent wrapper around the in-memory object, creating a made-up ID that
the object could use as if it were a real ID. This ID would be sent back to the
object, very much like the object-path-remapping result of an instantiation
action. And likewise, a call to context.trackedObjects.remove would likewise
get a special action added on the queue, requesting that the host release the
memory for the no-longer-needed object, and marking the object itself as no
longer having a valid path.
This design worked – and in fact, it still works today, if a developer chooses to
create a Request Context manually, via var context = new Excel.RequestContext(),
instead of a .run. But in practice, in both our internal testing and public pre-
view, it tuned out to be very tedious to have to call context.trackedObjects.add
on an object or two within nearly every scenario. And even when developers
did call it (with some trial-and-error), it was even more tedious (nay, unrealis-
tic) to expect that folks will remember and correctly dispose of the no-longer-
needed tracked objects.
In observing folks struggle with this tracked-objects concept, one thing that
became clear is that in the vast majority of cases, the developer’s intent is not
to keep the object around for some long-term storage – rather, the developer
generally just needs to track the object so that they can use it across one or
two sync boundaries, and then they are done with it forever. This is where the
Excel.run (Word.run, etc.) concept was born: to allow developers to declare
a single semantic unit of automation, even if internally it is comprised of a
series of sync-s. And for the framework to handle the tracking and untracking
silently.
This means that whenever you do an Excel.run (Word.run, etc.), after each
instantiation action there is also an action to track the object. And at the very
end, after a final flush of the queue at the completion of the Excel.run, there
is a separate internal request made to un-track every non-ID-able and non-
derivable object that had been created in the meantime. So the true picture
of the “actions” array from above is actually a bit more verbose than shown
earlier:
Implementation details, if you want to know how it really works 130
actions:
A1 => action: "init", object: "P2" (range)
A2 => action: "track", object: "P2" (range)
A3 => action: "method", object: "P2" (range)
name: "clear", args: <none>
A4 => action: "init", object: "P3" (thirdRow)
A5 => action: "track", object: "P3" (thirdRow)
A6 => action: "init", object: "P4" (format)
A7 => action: "init", object: "P5" (fill)
A8 => action: "setter", object: "P5" (fill),
name: "color", value: "purple"
And then, at the completion of the run, after a final flush of the queue, the
following would be sent (note that only the relevant object paths are being
sent; there is no need to carry extra baggage over the wire/process-boundary):
objectPaths:
P1 => global object (workbook)
P2 => (range)
parent: "P1", type: "method",
name: "getSelectedRange", args: <none>
P3 => (thirdRow)
parent: "P2", type: "method",
name: "getRow", args: [2]
actions:
A1 => action: "untrack", object: "P2" (range)
A2 => action: "untrack", object: "P3" (thirdRow)
And this – in a not so small nutshell – is how the underlying proxy objects
work, and how the runtime handles its communication to and from the host
application.
7. More core load concepts
Chapter structure
• Scalar vs. navigation properties – and their impact on load
• Loading and re-loading
• Loading collections
• Understanding the PropertyNotLoaded error
131
More core load concepts 132
KNOWLEDGE CHECK:
Which of the following are scalar properties?
Answers:
• #1-3 are all scalars, because they are all either primitive types
(strings/Booleans/numbers), or arrays of primitive types. In short,
they are just regular properties, not API objects.
• #5 is NOT scalar, because “font” is a real API object. How can you
tell? First, it has a load method, and a context property, both of
which are signs of deriving from the OfficeExtension.ClientObject
class. Second: its properties can be individually set (as opposed to
a complex object like SortField, which would have needed to be
set all in one go). Thus, while font doesn’t necessarily feel like a
real document object, it is no different from a Range or Worksheet or
Paragraph from a loading perspective: it is a proxy object, not a scalar
type.
When specifying properties to load, you can specify either scalar properties
either directly off the object, or scalar properties that are accessible via
navigation properties off the object. For example, to load both the cell address
and the fill color of a Range, you would specify the following (note that the
scalar property is always the “leaf-node” one – and any preceding slashes
indicate navigation properties):
myRange.load("address, format/fill/color");
Technically speaking, you are not required to include the property names.
You could (though you shouldn’t!) write a load statement with no prop-
erty names passed in (e.g., myRange.load() or context.load(myRange)). But if
you do that, Office.js will load all scalar properties on the object – which
on the Excel Range object, for example, is over a dozen! – even if you end up
using only one of them! Moreover:
BEST PRACTICE:
In addition to the “Golden Rule” above, which should prevent
you from loading unnecessarily, you should also avoid loading
unneeded properties. That is, even if a load is necessary, be sure
that you aren’t loading properties that you’re not using.
Be particularly wary of the “empty load” mentioned earlier –
that is, a load that does not call out the properties explicitly
(for example, range.load()). Such load statement is the equiv-
alent of loading all of the scalar properties on the object (that
is, for an Excel.Range object, it will load formulas, values, num-
ber formats, address, row count, column count, etc). The situ-
ation is even worse for collections, where an “empty load” will
also load all the scalar properties on the collection’s children, as
well. So, for example, an innocent-looking Word invocation of
document.body.paragraphs.load() will load the alignment, indents,
line spacings, spacings before and after, and of course the full
length texts of every single paragraph in the document!
The reason it’s called an “empty load” is because the properties
parameter to the load function is missing (empty). But in terms of
data traveling over the wire, it’s very much the opposite of “empty”.
Also, note that the alternate syntax of context.load(range) still con-
stitutes an empty load because the load-function off of the context
object expects two parameters, where the latter is the property(ies)
to load.
More core load concepts 138
Re-loading properties
1 // ...
2 // Initial loading of values:
3 myRange.load("values");
4
5 return context.sync()
6 .then(function() {
7 // ... Some operations that impact range values
8
9 // Re-load the cell values to retrieve the latest:
10 myRange.load("values");
11 });
12 // ....
1
For re-loading on collections instead of regular API objects, see the end of section
“Loading collections”. Essentially, re-loading a collection blows away any existing
objects and their properties, so you have to load everything from scratch – you can’t
just additively load a couple of new properties while maintaining the existing items.
More core load concepts 139
Loading collections is similar to loading regular objects – except that the prop-
erties you specify are the properties of the children. There are a few nuances
to collections in particular (see “Loading collections” for more details), but
the general use is easy enough. Simply call load on the collection, passing in
the names of the child properties. After syncing, you can access the items
using the collection’s items property. For example, to list out the names of
all worksheets in Excel, you would do:
• When loading, you specify the name(s) of the child property(ies), not
properties of the collection itself (of which there are usually very few,
typically just a .count, if that).
• When accessing loaded items, you use the .items property on the col-
lection. The items property returns a plain 0-indexed JS array containing
the loaded elements (with the specified properties pre-loaded).
• If you find it unsymmetrical to load the name child property, but use it as
items[x].name, you can specify the load statement as load("items/name")
instead. Internally, when the runtime sees this syntax, it simply strip
out the items/ portion of the load statement. It’s up to you which syntax
More core load concepts 140
you wish to use2 , but know that the canonical load statement is just
load("name").
You can load more than just one level deep on a collection. For example, to
enumerate all the column names on all the tables on all worksheets on a
workbook, you could do:
A multi-level load
11
12 console.log("The ridiculously-nested column name was " +
13 secondColumnOfFirstTableOnThirdSheet.name);
14
15 }).catch(...);
Note that above, the collection-item access has .items at each level in the
hierarchy:
worksheets.items[x].tables.items[y].columns.items[z].name
As mentioned above, if it’s easier for you to think of it in this structure when
loading as well, feel free to just add “items/” at each layer in your load
statement (which the runtime will then strip out, bringing the load statement
back to its canonical form3 ):
workbook.load("worksheets/items/tables/items/columns/items/name");
Finally, it is worth re-iterating that unlike with regular proxy objects, you
must load all the properties that you want on the collection at once, via a list
of comma-separated property names:
If the above code had the load statement split in two – one for name, and
the other for visibility – this wouldn’t just be a perf-hit, like it would for
a regular object. Instead, the second call would throw away any previously-
loaded items, and so name would no longer be loaded. And hence, you would
get:
Sooner or later – and at first, sooner and far more often than you’d like – you
will try to use a property, and get a PropertyNotLoaded error thrown instead.
Let’s step through what the error means, and how to avoid it.
From the perspective of JavaScript, the actual document data is stored some-
where far far away. It might be on the same device, albeit across a process
boundary, or – in the case of Office Online – it might be in a data center
hundreds of miles away from you. All that the JavaScript has are proxy
objects. So for a simple scenario like applying the fill color of one cell to
another – something that in VBA would be a one-liner – in the JS APIs you
have to do in three lines instead. Namely, given these two ranges:
You would need to first issue a command to load the desired property (color),
then do a sync at some point, and finally use the property’s loaded value:
source.format.fill.load("color");
await context.sync();
dest.format.fill.color = source.format.fill.color;
Let’s do another example, this time with the Excel Range’s values property,
and this time multiplying the value by 5 for good measure. The pattern is
identical, except that you have to remember that values is a 2D array, so
you’ll need to drill down into first column element of the first row it when
multiplying:
source.load("values");
await context.sync();
dest.values = [[ source.values[0][0] * 5 ]];
More core load concepts 144
To repeat: Unlike in VBA, where the entirety of the document was in-memory,
in the same process, at your program’s immediate disposal, the JS APIs
don’t have this luxury. You have to signal your intentions to want to read a
particular bit of data; you then have to wait for that data to be ferried across
(await context.sync()), and only then can you use it.
When working with API objects, it is crucial to take note whether you are
– worksheets.getItem("Sheet1") or
– workbook.getSelectedRange() or
– range.getIntersection(anotherRange)
– worksheets.items[0] or
– chart.title, or
– range.worksheet, etc.
Notice that for objects accessed via methods, them methods will almost
always be prefixed with a get – so be on a particular lookout for these.
The reason to care about the distinction is that method invocations – unlike
property access – always return a new object! Let me show this via two
examples: first the incorrect usage, and then the correct one.
The reason that this is an error is that you are retrieving the object anew the
second time when you reference it – and so you get a brand new proxy object,
which has no knowledge of the information you loaded on its twin. This, by
the way, is a very common mistake when working with collections: to first
fetch collection.getItem("key") and call load on it, then sync, and then re-
query collection.getItem("key") – with the latter being a brand new copy of
the original object, which defeats the purpose of having loaded the item to
begin with!
Thus, the proper way to use load on an object that was retrieved via a method
call is to keep a reference to the variable, and use it when reading back the
data, rather than re-fetching the item via the method.
Again, it’s important to call out that the guidance above is purely for objects
retrieved via a method call. In the case of property access, you don’t have
to store the intermediate navigation properties (unless you want to): after
the initial property invocation, the exact same instance of the object will be
returned time and time again. For example, the following is perfectly valid.
More core load concepts 149
Loading data and then re-querying a *navigation property* is perfectly fine, with no need for
intermediate variables
There is one easily-overlooked “gotcha” with the load method: it does not
throw an exception, even if you specify incorrect property names. Instead, it
simply no-ops on names that it doesn’t recognize.
So: if you think you’ve loaded a property, and you know you did a sync
upstream, and yet you’re still getting a “PropertyNotLoaded” error, check
your spelling! For better or worse, the load method always succeeds – even if
you pass in bogus property names! If load sees properties it doesn’t know
about, it simply ignores them, causing the property that you wanted to
stubbornly (and justifiably) insist that it hasn’t been loaded.
My trick for avoiding this issue is to always write out what I want to access
first, and then fill in an appropriate load and sync above. That is, if I want to
read the values of a range, I will first create a reference to the range object,
then go down a few lines, and write out the code that accesses the values
(using IntelliSense to guide me on the accessor):
With this code written, I will literally copy-paste the accessor portion into the
argument for a load statement (and also add a sync somewhere in-between):
More core load concepts 151
range.load("values");
await context.sync();
Though it may seem like obvious guidance, if you are getting the error despite
having called load and sync already, make sure that you’re calling the load
statement on the same object that you’re reading the property from. That is,
• Don’t re-fetch the object anew (see “Common mistake: re-invoking get
methods” above.
• Don’t call the load on a different object type from the object you’re
using (i.e., if you want to access range.values but instead call work-
sheet.load("values") – an actual real-life case that I’ve witnessed – can
you really blame Office.js for claiming that range.values hasn’t been
loaded?..)
More core load concepts 152
There is one more case where you may encounter the PropertyNotLoaded
error, despite having seemingly-corrected code. This case involves trying to
re-use an object outside the linear flow of Excel.run (Word.run, etc.). Please
see “A common, and infuriatingly silent, mistake: queueing up actions on the
wrong request context” for more information.
More core load concepts 153
The preceding chapter, and even an earlier section on proxy objects all
described how to load properties on regular API objects. There is one special
class of objects that do not need a load, which get loaded automatically as part
of a sync.
This type of object is a ClientResult4 , and it contains exactly one property:
value. You can think of it as a wrapper over an actual primitive value (string,
number, etc.). The only reason that a developer would ever fetch one of those
objects is to read back the value… and so we may as well just pre-fetch it.
When and how is a ClientResult returned? A ClientResult is typically the
result of a method call, where a logical type would be a primitive of some
sorts (generally a string). For example, on a Word Paragraph object, there are
getHtml() and getOoxml() methods5 . Likewise, on the Excel Chart object, there
is a getImage function that takes in the desired with, height, and fitting mode,
and returns a base-64 representation of the chart image.
For all three methods, you might well expect their return type to be a string.
That expectation is close to true – but if you look at their IntelliSense, you will
see them listed as returning ClientResult<string>6 . In other words, it’s not
a string object, but rather an object that wraps a value property that is the
string.
What is the purpose of this strange misdirection? It comes down to a technical
limitation, which is best explained with a concrete example.
4
For Excel API developers out there: don’t confuse OfficeExtension.ClientResult
with Excel.FunctionResult. The latter is just a regular API object, no different than
Table or Font: It contains more the one property (both value and error), and it can
be used as a parameter to a method call to chain formula values. In short, for
FunctionResult, you will need to load it explicitly just like you would any other proxy
object.
5
Ooxml stands for “Office Open XML” – the internal XML format of the document.
6
For those unfamiliar with this notation: the angle-brackets are TypeScript’s
syntax for generic types. In this particular case, it denotes that the returned object is
of type ClientResult, whose inner type – which in this case is the value property – is
a string.
More core load concepts 154
Suppose you have a chart proxy object. As a proxy object, it tries to create
the illusion of local methods and properties, but ultimately it’s only the host
application – during a context.sync – that can load the correct base64 string.
This means that if getImage were to return a primitive string object directly,
there would be no way for context.sync to later re-substitute the actual value
into the variable7 ! Instead, getImage must return a boxed type – which we call
ClientResult – so that after a context.sync, Office.js can update the result’s
value property. And that way, you would use the ClientResult as follows:
You may wonder, why have ClientResult-s at all, if regular properties were
good enough for nearly all the other properties in the OM (and are more
convenient to use, since you don’t need to create a separate variable for
them). There are two reasons for why a value would have been represented
as a ClientResult rather than a regular property.
2. The value is either very large (e.g., an OpenXML string for an inlined
image in a paragraph), or expensive to compute (e.g., HTML, which get
rendered on-the-fly during the API call). As you may recall from “The
Golden Rule of object.load”, if a developer forgets to specify property
names in an object.load() call, all scalar properties on the object get
loaded. This means that if such large or computationally-expensive
values were to be represented as properties on the Paragraph object,
a developers who only wants to read back the text property might
inadvertently load a megabytes’ worth of OOXML!
Chapter structure
• Real-world example of multiple sync-functions
• When to sync
• The final context.sync (in a multi-sync scenario)
• A more complex context.sync example
• Avoiding leaving the document in a “dirty” state
156
More core sync concepts 157
You are preparing for a parent-teacher conference. When you meet with
each parent, you want to be able to show them just the data for their
child, and filter the view to just assignments where the student got
a less-than-80% grade (so that you can discuss areas for improvement).
Let’s assume you’ll be doing all the filtering in-place: you’re not exporting
the data to new sheets or anything like that, it’s purely something that will
be used for discussion with one set of parents, before being re-filtered for
the next set of parents.
To give a concrete example: for the three assignments in the image above
(and let’s assume there’s a whole bunch more, omitted for brevity), when
talking with Matthias D’Armon’s parents, the teacher would only want to
show columns A (to see the student’s name) and columns B & D from the
assignments category. Column C, meanwhile, does not need to be shown, as
More core sync concepts 158
it’s the one assignment were Matthias got an over-80% grade (might I say he
had a “Eureka!” moment?) – and so this isn’t a problem area that the teacher
needs to discuss with Matthias’ parents.
This scenario is reasonably similar to the other two scenarios mentioned
earlier – but with the notable exception that here you only want to look at
one student’s data, which represents a small fraction of the data on the sheet. I
would argue that transferring a whole bunch of unneeded data is even worse
than doing an extra sync1 , so let’s see how we can do this task most efficiently,
even if it means fudging a bit on the minimal-syncs principle for the sake of
the avoiding-reading-copious-amounts-of-unneeded-data principle.
I think the most efficient breakdown of tasks is as follows:
Since we can’t proceed with any further operations without knowing the row
number (and it, in turn, can’t be read without first doing a sync), perform a
sync.
This seems like a reasonable plan, so let’s code it up. I encourage you to try
this out yourself as an exercise, before flipping to the following page and
seeing the finished code.
A three-‘sync‘ automation task, for filtering the cells to a particular student and his/her less-than-
stellar grades
37 studentRow.getCell(0, 0).select();
38
39 await context.sync();
40
41 }).catch(OfficeHelpers.Utilities.log);
1. You are done with the batch function, having read and/or manipulated
the document in whichever way the scenario saw fit. And so, this final
context.sync is your declaration of “Dear Excel / Word /etc: I’m done
working with you for the time being. Feel free to resolve the .run Promise,
and to clean up3 any resources that you so kindly provided to me. I’ll get
back to you when I need you again.”
2. You are in the middle of a batch function, and you can’t proceed
without reading back some data from the document, which you’ve
previously requested to be loaded. Because you’re dependant on the
loaded data, and the load can’t be fulfilled without a sync, you need to
sync.
3. You have a collection object (e.g., a Word RangeCollection object, re-
trieved by calling range.search(...)), and you want to perform an oper-
ation (i.e., call a function like range.getTextRange(...)) on each item. In
order for the collection items to be populated and be accessible via the
.items property, you do need to call the load method and then do a sync.
This scenario of wanting to populate items, without needing to read back
any properties, does in fact happen in real-life4 . My recommendation
is to just choose a single reasonable property (generally, id, text, or
name, whichever is applicable), call load with this property, and then
do a sync at some point before accessing the items. (Definitely don’t do
.load() with no properties specified, however, as that would load all
scalar properties, and would hence be much more wasteful.)
4. You were holding on to some object, but then needed to go off and do
some [possibly-lengthy] web calls, or have been waiting on some user
input. Depending on the scenario, and on how paranoid you are, it may
be worth to re-load and re-sync the data on the object in case it’s changed
in the meantime.
3
See “What does .run do, and why do I need it?” for details on what gets cleaned
up.
4
In fact, it happens in an example in this book – see “Re-hydrating an existing
Request Context” for a real-life example.
More core sync concepts 163
5. You are in the middle of a batch function, where you had requested some
really-temporal object (something like selection or active worksheet).
You want to get a reference to this fleeting object as soon as possible
(before the user changes his selection), so even though you don’t need
any data from the document, you do a sync to ensure that the identity of
the fleeting object is correctly captured.
6. [A corner-case]: You are in the middle of a batch function, and you’ve
queued up some “write” operations… but then you need to go and fetch
some information from the internet. You’re not awaiting on anything
from the document, but you feel that it would be kinder to your users
if you show them what work you have done, rather than having it all
queued up but un-dispatched 5 .
5
In practice, much as I don’t like to leave the user hanging, I don’t like presenting
him/her with a half-baked automation job, either. So in the vast majority of the cases,
I would not do a sync here – and in fact, I would ideally arrange my processing in
such a way that the web request happens first or last, without breaking up my OM
calls (just for aesthetics’ sake).
More core sync concepts 164
6
Or at least, you must be returning some Promise, where context.sync() is the
most natural one. Technically speaking, you could return a Promise via some other
means, such as having a return Promise.resolve() (but in that case, why not return
context.sync() for better clarity?) or returning a Promise that was obtained through
doing non-OM work, such as a web call (but in that case, why not do the web call
before the .run altogether)?
More core sync concepts 165
1 Excel.run(function(context) {
2 // ... {retrieve and load an object}
3 return context.sync()
4 .then(function() {
5 // ... {manipulate the object}
6 })
7 .then(context.sync)
8 })
9 .catch(...);
Just because you could omit it doesn’t mean that you should omit it, though
– and I’ll provide some reasons momentarily, for why I think including
the context.sync should be a best-practice. However, in case you end up
writing something like this by accident, and are bamboozled by how a sync is
seemingly unnecessary, be aware that:
Now, for why this works at all: before beginning the cleanup process at the
completion of the batch, the .run method will check if there are any pending
actions, and do an automatic sync if there is anything left in the queue. This
was done for developer convenience: it seemed inevitable that some of the
time, people would forget to do the last sync, causing their last chunk of code
to effectively no-op in a silent and unexpected way. As I am philosophically
against unexpected no-op-s (and while I would have been involved in the
design discussions anyway, in this case I was the one implementing .run),
we had two remaining choices: either throw an error or silently do the right
thing. From a teaching perspective, throwing an error would have been more
consistent, but it felt overly harsh, especially on beginners (who were more
likely to forget to add proper error-handling in any case). On the other hand,
More core sync concepts 166
it was obvious to guess the developer’s intent here; and it aligned nicely with a
quirky phrase from my previous team, of ensuring that if a developer makes
an error, he/she still falls into the pit of success. And so, we opted to do an
automatic syncing behavior if the last sync in a multi-sync batch is omitted.
That being said, I still think that it’s valuable to include the final context.sync
for the following reasons:
synchronous-om-call ⇒ sync;
synchronous-om-call ⇒ sync;
synchronous-om-call ⇒ sync.
2. It ensures that errors in the last portion of your batch will get bubbled
up correctly (they still will if you put a .catch outside of the .run, but in
practice I’ve seen a lot of developers put their .catch inside the batch
function).
3. If you or a colleague later decide to append more code, you will already
have the context.sync in place, thereby ensuring that you don’t forget
to add it when the need comes.
Whatever you do, the worst thing you can do is include a context.sync()
invocation but forget to return or await it. This would lead to a broken Promise
chain and all the evils therein. So, both with the last context.sync any any
other, remember that sync statements should always be awaited/returned.
More core sync concepts 167
If the code sample from a few pages ago – “Real-world example of multi-
ple sync-functions” – made sense, lets try something with even more sync-
functions, and that feels even more like a “real-world” scenario. [Conversely,
if the previous code sample did *not* make sense, it may be worth looking
over it again before proceeding with this one].
As in the previous example, before writing the code, let’s do a quick outline
of the steps (and sync-s) involved.
6. Having compared the current and previous totals, assign a green (posi-
tive) or red (negative) background to the current total cell, and issue one
final sync command to commit this action.
OK, enough planning. Let’s write some code! (Again, I recommend that you
try this yourself before looking at the finished code on the next pages).
More core sync concepts 170
37
38 console.log("#6: If there was a previous 'Total' to " +
39 "compare against, color the current one accordingly.");
40 addCellHighlightingIfAny(
41 latestTotalCell, previousTotalCellIfAny);
42 await context.sync();
43
44 console.log(`Done! Data on sheet "${latestSheet.name}" ` +
45 "has been refreshed");
46
47 }).catch(OfficeHelpers.Utilities.log);
And here come the helper functions, which either return an API object, or just
operate on the passed-in parameters:
1 function loadPreviousSheetTables(
2 sheets: Excel.WorksheetCollection
3 ) {
4 let sheetCount = sheets.items.length;
5 if (sheetCount >= 2) {
6 return sheets.items[sheetCount - 2].tables.load("name");
7 }
8 return null;
9 }
10
11 function updateTable(table: Excel.Table,
12 stocksRange: Excel.Range, data: any
13 ) {
14 let pricesToWrite = stocksRange.values.map(row => {
15 let stockName = row[0];
16 let priceOrEmptyString = data[stockName];
17 if (typeof priceOrEmptyString === "undefined") {
18 priceOrEmptyString = "";
19 }
20 return [priceOrEmptyString];
21 });
More core sync concepts 172
22
23 let priceColumn = table.columns.getItem("Price")
24 .getRange().getIntersection(stocksRange.getEntireRow());
25 priceColumn.values = pricesToWrite;
26 }
27
28 function loadPreviousTotalCellIfAny(
29 tables: Excel.TableCollection
30 ) {
31 if (tables && tables.items.length === 1) {
32 return tables.items[0].getTotalRowRange()
33 .getLastCell().load("values");
34 }
35 return null;
36 }
37
38 function addCellHighlightingIfAny(
39 latestCell: Excel.Range, previousCell: Excel.Range
40 ) {
41 if (previousCell) {
42 let isLatestGreater =
43 latestCell.values[0][0] >
44 previousCell.values[0][0];
45
46 latestCell.format.fill.color =
47 (isLatestGreater ? "#82E0AA" : "#EC7063");
48 } else {
49 console.log("Skipped comparison with previous " +
50 "total, as there doesn't appear to be one");
51 }
52 }
By the way, note that the last helper, addCellHighlightingIfAny, will sometime
add an OM operation to the queue, and sometimes not. In the latter case,
the await context.sync() in the master function will end up with no pending
actions – and that’s OK! The sync is smart enough to short-circuit and no-op
rather than making an empty round-trip to the host, so that’s not a problem
More core sync concepts 173
at all.
During the processing of the queued-up requests, suppose that the action to
retrieve the third row fails (e.g., the selection was a 2x2-sized range). In this
case, the clearing of the selection will already have been done, and so when
the execution aborts, the selection will remain cleared (even though no third
row ever got set to purple). While not ideal, this is no different from VBA or
VSTO with regards to Office automation; it is simply too difficult to roll back,
especially given any user or collaborator actions that may have happened in
the meantime. Excel is a user application with loose structure – it is not a
database or a bank ATM.
To this end, my recommendations for error-handling are as follows:
Chapter structure
• Checking via exception-handling – a somewhat heavy-handed
approach
• **A gentler check: the *OrNullObject methods & properties
176
Checking if an object exists 177
For the second issue, here’s a concrete example. Suppose you want to delete
a sheet if it exists, and then create a brand new one in its place (or just
create the new one). In fact, if you’ve been using Script Lab, you will see that
many Excel samples use a method from OfficeJsHelpers for this very scenario,
called ExcelUtilities.forceCreateSheet. Let’s derive our own version of it, if
all we had to rely on was just catch-ing errors:
Checking if an object exists 178
The code above, in slightly different form, is actually still used by Office-
JsHelpers as a fallback for older clients (though the OfficeJsHelpers one has
Checking if an object exists 179
An analogy
For variety’s sake, let’s take an example outside the realm of programming:
Let’s say you approach a llama at a petting zoo, or a camel in the desert
(happens all the time, right?). “Hey cute little buddy, can I pet you?”. Three
possibilities:
- Llama comes to you and nuzzles against your hand. Answer is “yes”.
- Llama looks at you, then shies and backs away: Answer is “no”.
- Llama spits at you. The answer is still “no”, but don’t you think it could
have been expressed in a gentler way?
1
What makes my code less safe is that I’m making an assumption that there will
be another visible sheet in the workbook when this function is called. Excel requires
that you always have at least one visible sheet, so if the existing sheet was the one
and only visible one, a failure would be thrown during the attempt to delete call (and
re-thrown by the catch). So the safer version, in the generic case, is to first create
the sheet (that way you’re guaranteed to have one), and only then attempt to do the
deletion; and finally, after the deletion or lack thereof, rename the sheet to what
you actually wanted it to be called. This is what I meant earlier in the book, when I
said that you should be an expert in the product that you’re automating… because
unexpected quirks like these are unavoidable, so you should be prepared to repeat
your automation steps manually, seeing what errors the UI throws at you (which will
often be more detailed than the API-provided ones).
Checking if an object exists 180
… And notably, this code will execute in a non-throwing fashion even if the
Report worksheet and/or the Summary table don’t exist.
This should surely sound strange. In fact, it very much is: Null Objects are
strange beasts, that defy normal conventions! In terms of taming and using
such objects, the scenarios fall into two categories:
If the reason that I was fetching the “Summary” table above was to delete it
if it exists and no-op otherwise – quite a common scenario, when you want to
ensure unique naming and re-export some data, I can just invoke the action
on it directly: table.delete(). Likewise, if I was fetching the table as part of a
2
See https://en.wikipedia.org/wiki/Null_Object_pattern
Checking if an object exists 181
general formatting routine (but am OK with no-op-ing for elements that don’t
exist in the document), I can just call just perform the call as if it’s a real object:
table.getRange().getColumn(0).format.font.bold = true
These calls will dutifully perform the requested action if the object (e.g.,
table) exists, and will silently do nothing if it doesn’t. This no-op behavior
was added specifically for chaining “get” commands (i.e., being able to fetch
the first column of a particular table on a particular worksheet, in and only
if the whole chain exists, without needing to do a bunch of null checks and
context.sync()-s). But, as you see above, this no-op behavior extends to any
other method calls or property value-setting, and can be particularly valuable
for the “do this if the object exists, and never-mind otherwise” behaviors.
Note that when chaining object accessors, there is a subtle but important
difference between the following two ways of accessing a table off of a null
object:
Checking if an object exists 182
• If both the “Dashboard” sheet AND the “SalesReport” table exist, then
both table1 and table2 will amount to the same thing (and would have
worked perfectly fine even with just a getItem for each).
• If the “Dashboard” sheet does NOT exist, both table1 and table2 will
also have identical behavior (this time resulting in a never-throwing
null object), as the null-ness of the missing “Dashboard” sheet gets
propagated to the “SalesReport” table regardless of how it’s fetched.
• Finally, if the “Dashboard” sheet exists BUT “SalesReport” does NOT,
you get diverging behavior. In the case of table1 (a getItem for a
missing table), the missing item will cause a throwing behavior, de-
spite being derived from a call that had a getItemOrNullObject in its
ancestry! Why? Because, once the sheet is fetched and proves to be
a real valid worksheet object, the fact that it came from an OrNul-
lObject function no longer matters (hence the getItemOrNullObject,
not a getItemAsNullObject)… And so the rest of its behavior (namely,
throwing when asked for a non-existent table) remains as is.
As you look at the above code, there is no doubt that the OrNullObject suffix
stands out. Could the Office Programmability team not have come up with a
shorter name? We undoubtedly could have – but truth be told, we wanted
to make it painfully and explicitly obvious that you’re entering the “null-
object” territory, and that, once there, things will go terribly awry if you’re
not expecting that object will gleefully do nothing when asked.
Checking if an object exists 183
This brings us to the second use cases for *OrNullObject methods, where
instead of just no-op-ing, you actually want to check whether you’re in null-
object land or not.
This brings us to the second use cases for *OrNullObject methods, where
instead of just no-op-ing, you might actually want to check whether you’re
in null-object land not.
In this second case, you actually do care about whether the object exists or not
– and so the use of *OrNullObject is merely a convenient way of confirming an
object’s existence. The advantage of using *OrNullObject methods for this use
case, over their throwing counterparts, is that you can simultaneously check
many different objects as part of one batch, and you do not need multiple
context.sync() calls for each level in the call hierarchy.
let intersectionIfAny =
sheet.getRange("C:C").getIntersectionOrNullObject(
context.workbook.getSelectedRange());
185
Excel.run (Word.run, etc.) advanced topics 186
4. Standardizes error-handing
Before the magic of async-await came to be, if you wanted to trap all errors in
code that had a mix of synchronous and asynchronous code, you would need
a standard try/catch block for the synchronous code, and then a separate
.catch(function(e) { ... }) handler for Promise failures. The Excel.run
(Word.run, etc.) method standardizes all errors, ensuring that if a runtime JS
error occurs before the first context.sync(), the exception is converted into a
rejected Promise, just as it would if it occurred after the first context.sync().
[Note that with TypeScript’s ‘async/await‘, this is no longer of particular
concern. You can use a ‘try/catch‘ block everywhere, for both synchronous
and asynchronous code, so long as you’re sure to ‘await‘ all your Promises].
Excel.run (Word.run, etc.) advanced topics 188
So far in this book, there was an implied assumption that the async nature of
the APIs is a technologically-necessary evil, but that the APIs try their hardest
to create an illusion of synchrony (even if punctuated by the occasional
pesky load and sync statements). Thus, everything in the book so far treated
scenarios as something that should execute in a linear (or seemingly linear)
fashion. The very guidance to await your context.sync()-s and Excel.run-s
stems from this assumption.
But what about the cases where you want intentional asynchrony? For ex-
ample, imagine that you’re implementing a search functionality in a Word
document, where you want to list out some matching content in a taskpane,
and then have the user be able to click on an item to perform some action on
it (color it, select it, etc.). In such scenario, the user’s click is going to come
at some indeterminate time after you’ve already finished doing the initial
Word.run that listed out the content. How do you resume using an object after
letting it go?
Having observed users struggle with this when we first released the original
Office 2016 APIs, we’ve added some convenience functions that you can use
in ExcelApi 1.2+ and WordApi 1.2+ (essentially, anything later than the MSI-
install of Office 20162 ). Even so, there are some subtle nuances that you need
to be aware of. So let’s take it one step at a time.
First, let’s begin with just the initial Word.run code that will populate the
search list:
Try it out
2
See the topic on Office versions.
Excel.run (Word.run, etc.) advanced topics 189
If you want to follow along, just import the following Snippet ID into Script
Lab:
dfb2693888d31062b636c65ac8c5259f
Now, onto the click handler. The trick is to do a Word.run just like you
normally would, but instead of having it create a new anonymous request
context, have the run resume using the context of some existing object.
To resume using an existing context, you simply use one of the function
overloads available off of Word.run; namely, an overload that takes in an
object (or array of objects) as the first argument, and the batch as the second:
1 ...
2 async function selectAndFormatRange(range: Word.Range) {
3 await Word.run(range, async (context) => {
4 range.font.highlightColor = "yellow";
5 range.select();
6
7 await context.sync();
8 });
9 }
10 ...
Excel.run (Word.run, etc.) advanced topics 191
Why do I mention error handling? Because if you write out and run the
above code – which I encourage you to do – you will notice that the code fails
to execute. If you have proper error-handling, you will at least get a hint for
why it is failing; namely, because of an InvalidObjectPath error3 .
Let’s explore why. The code is actually very close to correct: if the Word
or Excel Range objects had a persistent ID – just like Word content controls
and tables, or Excel worksheets and tables and charts – the code would “just
work”. But as described in the Implementation Details section, with regards
to “A special (but common) case: objects without IDs”, objects without IDs need
extra work to be kept in-memory. Inside of the linear flow of a Word.run, these
objects would automatically get tracked as needed. But, once the Word.run
Promise resolves, such objects automatically get cleaned up, to avoid leaking
memory and slowing down the host application. The JavaScript proxy objects
are still be available to the JavaScript runtime (following normal JS scoping
rules), so this cleanup is different than true runtime garbage collection. But
for all practical purposes, these objects becomes phantoms, a mere shadow
of the objects’ previously-glorious life. You can still read properties that have
3
Actually, it looks like even though Excel correctly throws the “InvalidObjectPath”
error for these cases, at the time of writing (July 2017), Word throws an “InvalidAr-
gument” error instead. There is a bug tracking this, to make the error behavior be
consistent and correct.
Excel.run (Word.run, etc.) advanced topics 192
already been loaded, but invoking any methods or setting any properties on
such object will result in an InvalidObjectPath error.
So: what do we need to do to fix this? It’s actually quite simple: inside the
Word.run code, in the places where it’s still linearly executing, we need to
declare our intention to make later use of these objects. To do this, we call the
.track() method on the necessary objects. This will make the objects exempt
from automatic cleanup.
The fix for ‘InvalidObjectPath‘: calling ‘object.track()‘ while still inside the linear flow of a
‘Word.run‘
Speaking of cleanup: This is where you pay the piper for tracking an
object. Once you call object.track(), the lifetime of the object becomes your
responsibility. Failure to clean up will result in the host application slowing
down over time, proportionally (and perhaps even exponentially) to the
number of leaked object. So, while several dozen leaked objects might go
reasonably unnoticed – and while certain scenarios, like this one, do require
you to reserve the memory for objects that you might eventually use – you
should do your best to avoid leaking memory unnecessarily. For example, if
the user re-clicks on the “search” button, and you bring down fresh results,
there is no way for the user to click on buttons that have now been erased
from the DOM. You should use this as an opportunity to clean up, so that you
aren’t leaking memory unnecessarily (and so that your users don’t experience
hangs that correlate with using your add-in). Let’s do one final edit to the code,
this time making sure to store the temporary Range objects into an array, and
cleaning up before we fill up the array with new objects:
Excel.run (Word.run, etc.) advanced topics 194
Note how in the cleanup code, we also use the Word.run overload that takes
in an object (or in this case, an array of objects). This way, the request for
untracking happens on the same context as the one that initiated the tracking.
We’ll see why it’s important in the very next section.
For the sake of argument, let’s take the code that we had in the previous
section, for selecting and formatting the range. But this time, let’s forget to
pass in the range object to Word.run, and use the normal Word.run instead of
the overloaded version.
Try it out, by importing the completed snippet from the previous section, and
omitting the range parameter in the await Word.run(...) statement:
What happens when we fail to pass in previously-used object to the ‘Word.run‘ overload?
and flushed only when its own context is synced. And so, if you create a new
anonymous request context as part of a regular (non-overloaded) Word.run,
and then use previously-created objects that had been part of an entirely
different context, the sync does nothing as far as the new actions on the old
objects are concerned.
Want an even more infuriating example? Let’s take a look at this code, this
time in Excel:
When you run this code, you will see the following error:
Despite knowing better, I’ve still managed to run into this issue occasionally.
My mental checklist for debugging these sorts of failures is as follows:
Excel.run (Word.run, etc.) advanced topics 198
1. “Am I using the right property name on the right object?”. To check, I
go to the line where I use the property, and see if it’s available in the
IntelliSense. It is indeed visible, so clearly a misspelling is not the issue.
Move onto the next step.
2. “Did I break the Promise chain?”. Nope, all run methods sync statements
are await-ed. Move on to the next step.
3. [Scratch head. What *is* the next step?]
4. [Glare silently at the screen, thinking ”*What do you mean that the
property isn’t available, can’t you see that I call ‘load‘ and ‘sync‘ above?
What else do you want me to do, say ’pretty please’?!*”]
5. [...Some time later:] “Oh yeah, it’s a previously-created object isn’t it?”.
Sure enough, I’m using an object from some past Excel.run, inside of a
new Excel.run and without specifying a property overload. Whew.
Fortunately, there is a very simple best-practice you can follow, to never end
up in this situation again:
Resuming with multiple objects is just as simple as resuming with one: just
pass in the array into the first parameter of the Excel.run (Word.run, etc.)
overload! This is what makes it so easy to follow the best-practice from the
preceding section, provided you are aware of the issue.
You’ve actually already seen a subtle use of this in one of the previous code
blocks:
A snippet from an example you’ve already seen before: passing an array of objects into the first
parameter of the ‘Word.run‘ overload
What happens if the objects are from different contexts? You will get an error,
as objects from different contexts can’t interact with each-other. But again, it’s
better to have the runtime throw the error at you, rather than having a silent
failure. And so again: as a best practice, always pass in any and all existing
objects that you intend to re-use within the new run scope.
Excel.run (Word.run, etc.) advanced topics 200
It’s the same exact error as you’d get if you tried mixing contexts in other
means (e.g., calling a method that uses an object from a different context).
Essentially, the array-accepting run overload is just doing extra pre-validation
for you, covering something that might or might not have been caught by the
runtime otherwise:
Excel.run (Word.run, etc.) advanced topics 201
The other reason for getting an ‘InvalidRequestContext‘ error: using objects from different
contexts in an API call
This also means that if you do want to make the above scenario work –
using a saved-off object in combination with newly-created objects in a new
Excel.run block – you just need to follow the same Best Practice as laid out
above, and pass in the existing range object to the second Excel.run invoca-
tion. Once you do, the code will run correctly, and output the appropriate
intersection address.
Excel.run (Word.run, etc.) advanced topics 202
This isn’t to say you shouldn’t use the run overloads: they are supremely
useful, and are much much easier than trying to manage object cleanup
manually (which is why the Excel.run concept was created in the first place).
Rather, I am merely suggesting that:
• On one hand, you follow the Best Practice, and always use the overload
if you’re re-using a previously-created object.
• On the other hand, make sure that you are indeed using the previously-
created object. If you aren’t, and it can be a simple fire-and-forget run
with no external dependencies, you’re better off letting the run create
and destroy its own new request context.
11. Other API Topics
• Office.initialize
• Finding the point of failure: Trace Messages
• Recognizing a broken Promise chain. Symptoms: code not executing
in order, errors silently swallowed, or InvalidObjectPath error.
• Collection access: .getItem(key), vs. .getItemOrNullObject(key), vs.
.getItemAt(index) vs. .items[index]
• Array properties & complex types (structs / interfaces)
203
12. The practical aspects of building
an Add-in
204
The practical aspects of building an Add-in 205
If you’re just getting started and want to use Visual Studio, I highly recom-
mend watching a walkthrough tutorial that I recorded in late 2015: https://
channel9.msdn.com/series/officejs/End-to-End-Walkthrough-of-Excel-JavaScript-
Add-in-Development.
In the video, I walk through the end-to-end process of building an Office Add-
in for Excel: from launching Visual Studio, to writing a bit of JavaScript code
that uses the new Excel 2016 APIs, to adding some basic UI tweaks, to talking
through the publishing options, debugging, and more.
The video touches on some API topics that are covered in much greater detail
in this book – but it also shows the process of creating a project and debugging
The practical aspects of building an Add-in 206
using Visual Studio, which is crucial for getting started. If you’ve not built an
Office Add-in before, I highly recommend the video.
For those looking for written instruction, just on the Visual Studio piece: there
is also official documentation for creating a project on https://dev.office.com/
docs/add-ins/get-started/create-and-debug-office-add-ins-in-visual-studio.
The practical aspects of building an Add-in 207
As noted earlier, I firmly believe that TypeScript (as opposed to plain JavaScript)
offers the premier Add-in coding experience. Depending on how comfortable
you are with the emerging web technologies (i.e., Node, NPM, etc), you can
either use the Office Yeoman generator to create a typescript-based project,
or you can tweak a Visual-Studio-created JS project to convert it to TypeScript.
Currently, the Visual Studio templates for Office Add-ins come only in a
JavaScript-based flavor. Fortunately, it does not take much setup to convert
the project to TypeScript. To do so, using Visual Studio, create the project as
if it’s a regular JavaScript-based Add-ins project, and then follow the few steps
described in this excellent step-by-step blog-post: http://simonjaeger.com/use-
typescript-in-a-visual-studio-office-add-in-project/. Once you’ve done it once,
it’s you’ll see that it only takes a minute or two to do the next time, and is well-
worth the trouble.
To get IntelliSense, be sure to add the Office.js d.ts (TypeScript definitions)
file, available from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/
master/types/office-js/index.d.ts1 . You can either copy-paste the file manually
into your project (and check back periodically to ensure that you have the
latest version), or install it via the @types/office-js NPM package, which will
make the package easier to keep up-to-date.
Note that to use the async/await features, you’ll need to ensure you have Type-
Script 2.1 or higher. For integration with Visual Studio, use Visual Studio 2017
(which comes with TS 2.1+ built-in), or download an extension for Visual Stu-
dio 2015 from https://www.typescriptlang.org/index.html#download-links.
1
Note that the file (and the rest of the files in the DefinitelyTyped repo) have
recently been changed to have the library’s main file be called “index.d.ts”. This
means that some of the older links you might encounter on the web will reference
“…/office-js/office-js.d.ts”, but in reality it should now be “…/office-js/index.d.ts”).
Likewise, whereas before the path was “…/master/office-js/…”, a more recent change
added a “types” folder inside of “master”. In short, be aware that while the file is
certainly on DefinitelyTyped, certain links to it may be broken – so use the latest URL
from above.
The practical aspects of building an Add-in 208
If you are comfortable with Node and NPM (Node Package Manager), you
may find it easier to use the Yeoman template-generator for Office instead.
Yeoman has been updated in early Feburary 2017 to include TypeScript
templates, and to offer a bunch of other goodness (e.g., browser-sync, and
the ability to auto-recompile TypeScript sources, etc). Yeoman also offers a
way to use Angular 2, instead of the plain html/css/js that comes with the VS
template. It requires a tad more setup, esp. the first time around (learning
where to trust the SSL certificate and how to side-load the manifest), but the
auto-recompilation, browser-sync, and the lightning speed of Visual Studio
Code (a web-tailored cross-platform editor, not to be confused with Visual
Studio proper) are worth it, if you don’t mind going outside the familiar Visual
Studio route.
The Add-in documentation team had put together an excellent step-by-step
README for how to use the Office Yeoman generator. You can find this doc-
umentation here: https://github.com/OfficeDev/generator-office/blob/master/
readme.md
Once you’ve installed the pre-requisites (Node, NPM), and also installed Yeo-
man and the Office Yeoman generator (npm install -g yo generator-office),
you can type yo office, and Yeoman will guide you through a series of
questions:
The practical aspects of building an Add-in 209
When it’s done, you will have a project set up, complete with all the goodness
of the NPM world and a couple of config files that wire the magic together.
Run npm start, and the web portion of the Add-in will launch. Once you’ve
done this, you need to do just a couple things:
When you’re ready to write some code, open the folder in your favorite editor.
Visual Studio Code is an absolutely excellent lightweight editor, which I have
been using as a companion (and often, my go-to tool) for web things. You can
even open a terminal straight within VS Code (use ctrl + ‘ [backtick])!
The really cool thing about using the Yeoman template and the browser-sync
functionality is that as soon as you make a change to the code and save, your
code gets automatically re-compiled and reloaded!
The practical aspects of building an Add-in 210
Note the auto-re-compilation of the files. The Add-in, too, will automatically re-load.
By the way, if you do not see IntelliSense when you type in something
like “Excel.” or “Word.” or when you try to modify something inside of
the Excel.run or Word.run block, please add a reference to the Office.js d.ts
(TypeScript definitions) file. To add it, simply run
npm install @types/office-js
The practical aspects of building an Add-in 211
IntelliSense is back!
The practical aspects of building an Add-in 212
On Windows, if you are using the Visual Studio templates for Office Add-ins,
the debugger will get automatically attached when you press Run (F5). It can’t
get simpler than that…
If you are not using Visual Studio (for example, you’re wanting to debug
a Script Lab snippet or are using a template created by the Yeoman-
generator and edited inside of VS Code), it’s no problem, either – but your
best bet is still to download and install Visual Studio, and use it for the
debugger component even if you are happy with your editor setup2 . To
attach the VS debugger to a running Add-in, you can either do an “Attach
to Process” from within VS (a cumbersome process involving first closing
down all Internet Explorer instances, and then attaching the debugger to
the remaining iexplorer.exe processes) – OR you can use a very cool recent
feature3 that makes the process much simpler. Simply click on the Personality
menu at the top right of the Add-in, and select “Attach Debugger”, as follows:
2
I have heard that some folks have had success with Windows 10’s standalone
F12 tool. So, if you are opposed to having Visual Studio on your computer (and don’t
have it free courtesy of an MSDN subscription, BizSpark, etc.), you probably do have
this option – but it won’t be integrated with the “Attach Debugger” menu, and I’m not
100% sure if it’s quite as powerful as its VS counterpart.
3
How recent is recent? You will need an Office 365 install of Office for Window,
build number 77xx.xxxx or later. See https://dev.office.com/blogs/attach-debugger-
from-the-task-pane for more info.
The practical aspects of building an Add-in 213
A little while later, Visual Studio will launch, with your web assets in the
Solution Explorer. Simply find the script file you’re interested in, and add
a breakpoint at the desired spot.
The practical aspects of building an Add-in 214
An active breakpoint. By the way, note how the TypeScript ‘async/await‘ gets compiled down to
pretty strange-looking code, to create the illusion of synchronous commands!
DOM Explorer
Regardless of whether you attach the debugger through Visual Studio’s “Run”
or through the Add-in, there is another very useful tool to use while the Add-in
is under the debugger. Tucked away like a hidden gem, the DOM (Document
Object Model) Explorer allows you to view and modify the HTML content
of your Add-in, live. Personally, I find this tool to be indispensable when
debugging an Add-in layout issue. To launch it, simply choose the “Quick
Launch” textbox at the top-right of VS, enter in “DOM”, and select the “Show
All” match.
The practical aspects of building an Add-in 215
12.4 IntelliSense
… And of course, you can always try out the APIs – IntelliSense and all – in
Script Lab. See “Script Lab: an indispensable tool” from earlier in the chapter.
The practical aspects of building an Add-in 217
12.5 Office versions: Office 2016 vs. Office 365 (MSI vs.
Click-to-Run); Deferred vs. Current channels;
Insider tracks
To develop and distribute Add-ins that use the new Office 2016 API model,
you need either Office 2016 or Office 365 (the subscription-based superset
that includes all 2016 features). This seems reasonably straightforward, but
the devil is in the details.
In the next few pages, I will describe the different installation possibilities. It
may help to bear the following image in mind:
Office 365 is the brand name Microsoft uses for a group of software
and services subscriptions, which together provide productivity
software and related services to subscribers. For consumers, the
service allows the use of Microsoft Office apps on Windows and
macOS, provides storage space on Microsoft’s cloud storage service
OneDrive, and grants 60 Skype minutes per month. For business
users, Office 365 offers service plans providing e-mail and social
networking services through hosted versions of Exchange Server,
Skype for Business Server, SharePoint and Office Online, inte-
gration with Yammer, as well as access to the Microsoft Office
software.
So, for those coming from the SharePoint world: yes, SharePoint Online is part
of an Office 365 subscription, as are the Office Online in-browser editors that
come with it. But, it is not the only part of the subscription. Getting access to
the same desktop/mac Office programs that you know and love is also part of
that same subscription (as is getting iOS and Android versions of those Word,
Excel, PPT, etc. programs).
Now, back to the APIs: if you have Office 2016 (non-subscription), you will
only have the initial set of the new wave of Excel and Word APIs (ExcelApi 1.1
and WordApi 1.1). Or to put it even clearer: you will only have the initial set
of extensibility functionality – period. So in addition to missing improvements
to the Excel and Word APIs, you will also lack other add-in functionality like
the ability to customize the Ribbon or launch dialogs.
It’s also worth noting that the original RTM offering of the APIs did have some
bugs. Some were more innocent than others4 , but – in my personal opinion –
I would treat RTM as more of the start of a journey into rich host-specific APIs,
rather than a destination of its own.
4
There are a number of bugs that come to mind. On the Excel side, for example,
reading back values (even without manipulating the document) would blow the
undo stack – not a catastrophic issue, but irksome all the same. On the Word side,
there were some issues with document-identity when accessing items in a collection
(e.g., paragraphs), whereby the proxy object remembered its index in the collection,
but not the actual document entity that it belonged to; and so, if the document was
manipulated, the proxy object (paragraph) would now point at the wrong item.
The practical aspects of building an Add-in 220
team, before the APIs get cemented and go live! To become an Insider, see
https://products.office.com/en-us/office-insider.
Office 365 flavors for Enterprise
For users of the enterprise / business flavor of Office 365, there are also
a number of options (typically handled by the IT administrator). Like with
the Consumer versions, there is a “Current” channel (latest-and-greatest
stable build, updated monthly) – and similarly, there is a “First Release for
Current channel”, which essentially is the same as the “Insider” builds in the
consumer version.
However, risk-averse enterprises may also choose to be on a Deferred chan-
nel, which updates once every four months instead of once every month.
Moreover, these enterprises can also stay on the Deferred channel for four or
even eight months, before jumping ahead to a newer build. Thus, a business
on the Deferred channel may still be a fair bit behind the developer in terms
of what API functionality is available (though less behind than someone on
the RTM build of Office 2016).
Office on other platforms (Mac, iOS, Online)
For non-PC platforms, there is also a span of time before different function-
ality lights up. This is sometimes dependent not just on the delay between
something being code-complete and getting in front of customers’ hands (i.e.,
the difference between Insider and Current and Deferred), but also on the
order in which functionality gets implemented on these platforms. For the
Excel APIs to date, I have seen them light up on most platforms at roughly
the same time; for Word, the Desktop has generally been ahead of Online. For
the non-API functionality (i.e., dialogs, ribbon extensibility), these have also
generally come to the Desktop first, followed by Online and Mac. The different
speeds of implementation is why it’s important to keep in mind not just Office
host versions, but also API versions and Requirement Sets – the subject of the
very next section.
The practical aspects of building an Add-in 222
The more interesting bit for versioning are the actual API capabilities that
are offered by each host. Just because you have the latest and greatest
JavaScript does not mean that older clients will be able to make use of all of it.
While some of your customers might be on the latest-and-greatest versions of
Office, others won’t be! In particular, Office 2016 RTM will always only have
access to the original set of Office 2016 APIs – period. And even customers who
own subscription versions of office (Office 365 / click-to-run) might still be on
older versions of the supposedly-evergreen version, depending on whether
5
Note that even though your HTML page will reference Office.js, in practice
that file is only a loader – it will then go and load the actual host-specific files,
such as https://appsforoffice.microsoft.com/lib/1/hosted/excel-web-16.00.js. So if, for
whatever reason, you are looking at the text headers of the JS files, note that the
interesting version number is that of the host-specific file, not of Office.js itself.
The practical aspects of building an Add-in 223
they are on the Current Channel or the Deferred Channel of Office 3656 .
This intersection of native-application-versioning to javascript-versioning is
where things get trickly.
The solution to this complexity (albeit complex in its own right) is Require-
ment Sets. For example, if you look at the Excel API sets documentation7 ,
you will see that the 2016 wave of Excel APIs has had three versions as
of December 2016: 1.1, 1.2, and 1.3. ExcelApi 1.1 was what shipped with
Office 2016 RTM in September 2015; 1.2 shipped in early March 2016; and
1.3 shipped in October of 2016. Each API set version has a corresponding
Office host version that supports this API set. The version numbers are listed
in the table, and there are links below the table to find a mapping from build
numbers to dates.
Each of the API set versions contain a number of fairly large features, as
well as incremental improvements to existing features. The topic for each
requirement set, such as the link above, will provide a detailed listing of each
of those features. And as you’re programming, if you are using the JavaScript
or TypeScript IntelliSense, you should be able to see the API versions for each
of your APIs displayed as part of the IntelliSense:
6
Being on a Deferred Channel is an option offered to business customers, allowing
them to lock in to older versions and defer the monthly updates until they’ve had
sufficient time to test out the new features. See https://technet.microsoft.com/en-us/
library/mt455210.aspx.
7
https://dev.office.com/reference/add-ins/requirement-sets/excel-api-
requirement-sets
The practical aspects of building an Add-in 224
You can use the requirement set in one of two ways. You can declare in the
manifest that “I need API set ExcelApi 1.2, or else my add-in doesn’t work at
all” – and that’s fine, but then of course you aren’t able to service older hosts,
and so your add-in won’t even show up there. Alternatively, if you add-in
could sorta work in a 1.1 environment, but you want to light-up additional
functionality on newer hosts that support it, you can use the manifest to
declare only your minimal API sets that you need (e.g., ExcelApi 1.1), and
then do runtime checks for higher version numbers via the isSetSupported
API.
For example, suppose that you are exporting some data to a new sheet, and
you’d ideally like to autofit the column width – but this is only available in
ExcelApi 1.2. Rather than block the add-in outright from running on an older
host, you can do a light-up scenario on newer hosts that support the API, and
skip over the functionality otherwise:
1 Excel.run(context => {
2 let data = [
3 ["Name", "Phone number"]
4 ["Joe Doe", "425-123-4567"],
5 ...
6 ]
7 let newSheet = workbook.worksheets.add();
8 let dataRange = newSheet.getCell(0, 0)
9 .getResizedRange(values.length - 1, values[0].length - 1);
10 dataRange.values = data;
The practical aspects of building an Add-in 225
11
12 if (Office.context.requirements.isSetSupported("ExcelApi", 1.2)) {
13 dataRange.format.autofitColumns();
14 }
15
16 await context.sync();
17 })
To use the beta endpoint, simply change out the script reference, and try out
some APIs that you see in the open spec. Some might be ready; others might
not. If you encounter a JavaScript runtime error to the tune of a nonexistant
object or function, it’s not part of the Beta endpoint yet; if the JavaScript is
OK but you get an “ApiNotFound” error, you do not have a sufficently recent
build (or perhaps no such build exists, yet). Desktop, and sometimes Office
Online, are the first two channels where the APIs are made available during
the beta stage; the other platforms migth only receive the APIs once they roll
into production.
There are a few caveats to the beta endpoint:
All this being said: As you become more familiar with the platform, and
especially as you see features that you care about become open-spec-ed, I
strongly encourage you to participate in the feedback process, and to use the
beta endpoint to validate that the design meets your needs. And as always,
questions or feedback on StackOverflow about the APIs – whether production
or beta – are always welcome.
Essentially, the script above will return true for any API at all that is part of
the API set that you want the latest-and-greatest beta functinality of (in this
The practical aspects of building an Add-in 228
An API is production-ready when you look at the IntelliSense, see what API set
version the API is supposed to be a part of, do isSetSupported on that version
number, and see it return true.
This moment should roughly correspond with:
Note that some platforms might be ahead of others, with Desktop generally
leading. So just because isSetSupported returns true on Desktop, doesn’t
mean that it will necessarily return true on the Mac at the very same moment
(though it generally will, within a month or two’s time). But the different
cadence of implementation is precisely why isSetSupported (and/or manifest-
based requirements specification) is needed in the first place. As long as
you surround any light-up functionality with isSetSupported, or add the
manifest requirement, you shouldn’t need to worry about what APIs are
made available when; when the functionality becomes available on the given
platform, your add-in will “just work” there.
13. Appendix A: Using plain ES5
JavaScript (no async/await)
Chapter structure
• Passing in functions to Promise .then-functions
• JavaScript-only version of the canonical code sample
• JavaScript-specific sync concepts
230
Appendix A: Using plain ES5 JavaScript (no async/await) 231
JavaScript-only
This section is thematically related to the “Promises primer“ sec-
tion, coverting a technique that is very useful to for folks writing
in pure JavaScript, but unnecessary for folks who are coding in
TypeScript and with async/await. TypeScript and its async/await
syntax don’t need to use a .then, so the issue goes away on its own.
1 performAsyncOperation()
2 .then(function() {
3 // ...
4 // some synchronous operations
5 // ...
6
7 // return eiher a Promise or a regular value
8 return xyz;
9 })
10 .then(...)
11 // ... {rinse and repeat}
12 .catch(...)
In the above, the function that’s passed in to the first .then is known as an
anonymous function – that is, a function whose body has been inlined into
the code, as opposed to being isolated out into its own standalone function.
Depending on the size of the anonymous function, and especially if that
function continues to evolve, you might find it more convenient to isolate
out the logic into its own separate named function, as follows:
Appendix A: Using plain ES5 JavaScript (no async/await) 232
1 function myFunction() {
2 // ...
3 // some other operations
4 // ...
5 // return xyz;
6 }
There are two ways to make use of this function, now that it’s been isolated
out. One way is to still keep the anonymous function, but do a one-liner
invocation of the named function from within the anonymous one (and not
forget the return statement!):
1 performAsyncOperation()
2 .then(function() {
3 return myFunction();
4 })
5 .then(...)
6 .catch(...)
There is nothing wrong with doing the above – assuming you remember
the return statement, that is. But if you pause to analyze the code, you’ll
notice that all you’re doing is creating an anonymous function which takes
in no arguments, and then invoking a named function that also takes in no
arguments[^arguments]. Functions are first-class citizens in JavaScript, so
rather than creating a new anonymous function that all it does is return the
value of a function call to another function, you can use the function directly
in the .then statement:
Appendix A: Using plain ES5 JavaScript (no async/await) 233
1 performAsyncOperation()
2 .then(myFunction)
3 .then(...)
4 .catch(...)
JavaScript-only
This section reflects a JavaScript (no-TypeScript) version of the
“Canonical code sample“ (see the original section for information
on the scenario that this code addresses)
The major difference to note is that instead of using async/await,
the code uses a series of .then-s and a final .catch. This is certainly
possible, and the next few sections will show specific JS-based
techniques for making this quite a reasonable approach. But for
myself, with TypeScript’s async/await now available, I find that it’s
a fair bit easier to use TypeScript instead.
22 latestDataColumn.load("values");
23 earliestDataColumn.load("values");
24
25 return context.sync()
26 .then(function () {
27 // Create an in-memory data representation, using an
28 // array with JSON objects representing each city.
29 var citiesData = [];
30
31 // Start at i = 1 (that is, 2nd row of the table --
32 // remember the 0-indexing) to skip the header.
33 for (var i = 1; i < nameColumn.values.length; i++) {
34 var name = nameColumn.values[i][0];
35
36 // Note that because "values" is a 2D array
37 // (even if, in this case, it's just a single
38 // column), extract the 0th element of each row.
39 var pop1990 = earliestDataColumn.values[i][0];
40 var popLatest = latestDataColumn.values[i][0];
41
42 // A couple of the cities don't have data for 1990,
43 // so skip over those.
44 if (isNaN(pop1990) || isNaN(popLatest)) {
45 console.log('Skipping "' + name + '"');
46 }
47
48 var growth = popLatest - pop1990;
49 citiesData.push({ name: name, growth: growth });
50 }
51
52 var sorted = citiesData.sort(function(city1, city2) {
53 return city2.growth - city1.growth;
54 // Note the opposite order from the usual
55 // "first minus second" -- because want to sort in
56 // descending order rather than ascending.
57 });
58 var top10 = sorted.slice(0, 10);
59
Appendix A: Using plain ES5 JavaScript (no async/await) 236
98
99 // Finally, with the table in place, add a chart:
100 var fullTableRange = table.getRange();
101
102 // For the chart, no need to show the "Rank", so
103 // only use the column with the city's name -- and
104 // then expand it one column to the right
105 // to include the population data as well.
106 var dataRangeForChart = fullTableRange
107 .getColumn(1).getResizedRange(0, 1);
108
109 var chart = outputSheet.charts.add(
110 Excel.ChartType.columnClustered,
111 dataRangeForChart,
112 Excel.ChartSeriesBy.columns);
113
114 chart.title.text =
115 "Population Growth between 1990 and 2014";
116
117 // Position the chart to start below the table,
118 // occupy the full table width, and be 15 rows tall
119 var chartPositionStart = fullTableRange
120 .getLastRow().getOffsetRange(2, 0);
121 chart.setPosition(chartPositionStart,
122 chartPositionStart.getOffsetRange(14, 0));
123
124 outputSheet.activate();
125 })
126 .then(context.sync);
127
128 }).catch(function (error) {
129 console.log(error);
130 // Log additional debug information, if applicable:
131 if (error instanceof OfficeExtension.Error) {
132 console.log(error.debugInfo);
133 }
134 });
Appendix A: Using plain ES5 JavaScript (no async/await) 238
Try it out
If you want to follow along, just import the following Snippet ID into Script
Lab:
98d04bc5293e027c84c8c03741698a94
Appendix A: Using plain ES5 JavaScript (no async/await) 239
pitfall and a way to get around it. Because a batch will typically contain two
or more sync calls – each of which returns a Promise – it is pivotal NOT to
break the Promise chain, or else you may encounter some unexpected and
often hard-to-diagnose behavior (see “Chaining Promises, the right way”). This
means that:
Remembering #2, on the other hand, takes more effort. There is unfortunately
no error-checking that can catch the issue for you, as it’s perfectly reasonable
(and very common) to have a .then-function that doesn’t return any values
(i.e., is void). And moreover, when you do forget the return statement, you
end up in non-deterministic territory, where the code might or might not work
based on timing; and where diagnosing the issue is notoriously difficult (with
a broken Promise chain, you also lose error-handling; yay!).
Having unintentionally broken the Promise chain myself, on a number of
occasions – and having seen beginner Office.js developers break the Promise
chain time and time again – I personally prefer to sidestep the issue alto-
gether, using the technique covered in “Passing in functions to Promise .then-
functions”. Namely, I make all functions that make OM calls be synchronous
(i.e., only queue up the operations, but not dispatch the context.sync()
yet), and then I follow up these synchronous “.then”s with corresponding
.then(context.sync)-s. Notice how the latter does not invoke context.sync
(there are no () after sync, as you’re passing in a function reference, not a
Appendix A: Using plain ES5 JavaScript (no async/await) 241
return value). And you can’t be guilty of forgetting a return statement, if there
is physically no return statement to begin with!
In case the above wasn’t 100% clear, let me take a moment to “derive” this
transformation:
Let’s start at the beginning. Imagine you have code like:
...
return context.sync()
.then(function() {
// ... synchronous OM code
return context.sync();
})
.then(...)
This code makes some OM calls that queue up some operations, and then it
invokes context.sync() in an “invoke-style” sync. If we wanted to, we could
split out the two parts of the code into two consecutive “.then”s: one for the
synchronous OM code, and one for the asynchronous sync invocation. There
isn’t much sense in doing this, but it’s just an intermediary process in our
derivation:
...
return context.sync()
.then(function() {
// ... synchronous OM code
})
.then(function() {
return context.sync();
})
.then(...)
Now, all that the second .then does is create an anonymous function whose
sole purpose is to return the result of an invocation of another [named]
function. This is needlessly complex. Since functions are first-class citizens in
JavaScript, we can simplify the second .then to take the context.sync function
directly, “reference-style”, without an invocation!
Appendix A: Using plain ES5 JavaScript (no async/await) 242
...
return context.sync()
.then(function() {
// ... synchronous OM code
})
.then(context.sync)
.then(...)
Putting this all together, it means that instead of all the “invoke-style” calls –
with the risk of forgetting a return-statement – in the illustration in the pre-
ceding section, you can now use the “reference-style” approach to dispatch
the sync in a safer manner:
If you follow the transformation prescribed by this pattern, you’ll find that
typically, the only return statement you’ll need is the first return context.sync()
invocation – which, between the TypeScript compiler and the runtime error,
should be relatively straightforward. For the rest, the problem is avoided
altogether via the “reference-style” approach.
Appendix A: Using plain ES5 JavaScript (no async/await) 243
If you’re doing more than two syncs as part of your Excel.run batch, you’ll
often need to pass in a variable from one .then to another. I am explaining
this here, before showing an end-to-end example, because you can’t do the
example without having to pass some values across the .then boundaries.
First, let’s look at an example where you don’t have a problem: the only-two-
sync-functions case:
The **successful** case: variables declared outside (before) the first ‘return context.sync()‘ can
be accessed within a ‘.then‘
1 Word.run(function(context) {
2 var selection = context.document.getSelection();
3 selection.load(...)
4
5 return context.sync()
6 .then(function() {
7 // The "selection" object is in scope (it was defined
8 // at a broader scope that this function shares),
9 // so you can use it here all you want:
10
11 if (selection.values === ...) {
12 ...
13 }
14 })
15 .then(context.sync);
16 }).catch(...);
The problem begins to manifest itself when you have three or more sync-
functions, which – if you follow the pattern described in the previous section
– get punctuated by two or more “.then”s. The problem is that there isn’t a
seamless way to pass data from one .then to another: the variables declared
within a given .then are out-of-scope for the next .then:
Appendix A: Using plain ES5 JavaScript (no async/await) 244
1 Excel.run(function(context) {
2 var selection = context.workbook.getSelectedRange();
3 selection.load(...)
4
5 return context.sync()
6 .then(function() {
7 // Based on reading the selection values,
8 // choose to load some other data
9 var subsetRange = selection.getColumn(...);
10 subsetRange.load(...);
11 })
12 .then(context.sync)
13 .then(function() {
14 if (subsetRange.values[0][0] = ...) {
15 ...
16 }
17
18 // The above "subsetRange.values" call will fail,
19 // because the earlier assignment of subsetRange
20 // is out of scope. You will get an error:
21 // "ReferenceError: 'subsetRange' is undefined"
22 });
23 }).catch(...);
APPROACH #1: Instead of chaining the “.then”s as siblings, nest them instead.
The main disadvantage is that the code gets more and more indented, a-la the
callback / pyramid-of-doom style. Moreover, you must not forget the return
keyword before each context.sync() invocation, or else you end up in non-
deterministic territory. BUT, this does work, and – if used sparingly – can
sometimes be the most elegant solution.
1 Excel.run(function(context) {
2 var selection = context.workbook.getSelectedRange();
3 selection.load(...);
4
5 return context.sync()
6 .then(function() {
7 var subsetRange = selection.getColumn(...);
8 subsetRange.load(...);
9
10 return context.sync()
11 .then(function() {
12 if (subsetRange.values[0][0] = ...) {
13 ...
14 }
15 });
16 });
17 }).catch(...);
Appendix A: Using plain ES5 JavaScript (no async/await) 246
APPROACH #2: Declare your variables before the first return context.sync(),
so that they remain in scope. This is not a bad option. You have to remember
to do your variable declarations a fair distance above where you’re going to
be using them, but that’s not too bad. The main disadvantage is IntelliSense:
having a var subsetRange declaration in one place, and the assignment in
quite another, will render the JavaScript IntelliSense experience useless, as
the VS IntelliSense engine will no longer be able to deduce the variable’s
type. On the other hand, if you’re using TypeScript, you can annotate the
variable declaration as var subsetRange: Excel.Range, and thereby gain your
IntelliSense back. Personally, I find this option much more palatable than the
first.
Approach **#2**: Declaring variables ahead of the first ‘return context.sync()‘ to preserve scope
1 Excel.run(function(context) {
2 var selection = context.workbook.getSelectedRange();
3 selection.load(...);
4
5 // Declaring the variable ahead of its use
6 var subsetRange;
7
8 return context.sync()
9 .then(function() {
10 // Assigning to the previously-declared,
11 // scope-defying variable:
12 subsetRange = selection.getColumn(...);
13 subsetRange.load(...);
14 })
15 .then(context.sync)
16 .then(function() {
17 // Hooray! "subsetRange" can now be used!
18 if (subsetRange.values[0][0] = ...) {
19 ...
20 }
21 });
22 }).catch(...);
Appendix A: Using plain ES5 JavaScript (no async/await) 247
APPROACH #3: Pass variables through on the return value. If you have a
variable like subsetRange inside a .then, you can do a return subsetRange,
close off the function, follow it up with a .then(context.sync), and have the
value be passed-through as a parameter to the next function!
1 Excel.run(function(context) {
2 var selection = context.workbook.getSelectedRange();
3 selection.load(...);
4
5 return context.sync()
6 .then(function() {
7 var subsetRange = selection.getColumn(...);
8 subsetRange.load(...);
9 return subsetRange;
10 })
11 .then(context.sync)
12 .then(function(subsetRange) {
13 // Hooray! "subsetRange" can now be used!
14 if (subsetRange.values[0][0] = ...) {
15 ...
16 }
17 });
18 }).catch(...);
• The naming of the passed-in variable (line #12) need not match the
naming in the previous .then (though I generally would make them
match; coming up with a good variable name is hard enough, so why
do it twice?!). Obviously, if you are returning the object as per the
Appendix A: Using plain ES5 JavaScript (no async/await) 248
suggestion above, having never named it, the point becomes moot – and
so the incoming parameter name is the place where you name it.
• If you want to pass multiple objects, you can. Admittedly, at that point
it gets a little messy, so I personally would switch back to Approach #2
for this. But, you can return a complex object, as in
return { subsetRange: subsetRange, table: table }
On the receiving end of the subsequent .then, just declare the function
as accepting a data parameter, and access the passed-in objects as
“data.subsetRange”, “data.anotherObject”, etc.
Finally, APPROACH #4: Use the async/await feature of TypeScript 2.1… and
sidestep the nesting problem altogether.
Personally, TypeScript’s async/await feature aside, I prefer to use approach
#3 for single-variable pass-through’s, and #2 for everything else. However,
I should note that I do most of my web coding in TypeScript, where type
information can either be inferred or declared. If I were doing pure JS
development, I would probably switch to approach #1, just for IntelliSense’s
sake… or, better yet, convert the project to TypeScript, to reap the IntelliSense
benefits and more!
Appendix A: Using plain ES5 JavaScript (no async/await) 249
The code below shows the pure-ES5-JavaScript approach to the scenario de-
scribed in section “Real-world example of multiple sync-calls”. The automation
tasks will filter out all rows and columns, exposing only the row with the
student, and only the columns where he/she received less-than-stellar grades.
1 Excel.run(function(context) {
2 // #1: Find the row that matches the name of the student:
3
4 var sheet = context.workbook.worksheets.getActiveWorksheet();
5 var nameColumn = sheet.getRange("A:A");
6
7 var studentName = $("#student-name").val();
8 var matchingRowNum = context.workbook.functions.match(
9 studentName, nameColumn, 0 /*exact match*/);
10 matchingRowNum.load("value");
11
12 var studentRow; // declared here for passing between "`.then`"s
13
14 return context.sync()
15 .then(function() {
16 // #2: Load the cell values (filtered to just the
17 // used range, to minimize data-transfer)
18
19 studentRow = sheet.getCell(matchingRowNum.value - 1, 0)
20 .getEntireRow().getUsedRange();
21 studentRow.load("values");
22 })
23 .then(context.sync)
24 .then(function() {
25 // Hide all rows except header ones and the student row
26 var cellB2AndOnward = sheet.getUsedRange()
27 .getOffsetRange(1, 1);
28 cellB2AndOnward.rowHidden = true
29 cellB2AndOnward.columnHidden = true;
30 studentRow.rowHidden = false;
Appendix A: Using plain ES5 JavaScript (no async/await) 250
31
32 for (var c = 0; c < studentRow.values[0].length; c++) {
33 if (studentRow.values[0][c] < 80) {
34 studentRow.getColumn(c).columnHidden = false;
35 }
36 }
37
38 studentRow.getCell(0, 0).select();
39 })
40 .then(context.sync);
41 }).catch(OfficeHelpers.Utilities.log);
Note the return context.sync() call on line #14 (where the return statement
ensures that the value returned out of the batch function is a Promise), and
then the two .then(context.sync)-s on lines #23 and #40.
While the code above accomplishes the original scenario, it’s worth pointing
out (am I beating a dead horse?) some aspects that are less ideal compared to
the TypeScript version:
This both aids the IntelliSense engine, and allows the compiler to catch
misspellings, type errors, and so forth.
Chapter structure
• Script Lab: the story behind the project
252
Appendix B: Miscellanea 253
Script Lab is brought to you by this group of good-looking geeks (with myself on bottom right).
Photo from https://aka.ms/scriptlab.
To read more about Script Lab, or to visit links to videos and other media
content about the project, see the project’s GitHub repository and README:
https://aka.ms/script-lab