We continue our journey through Apps for Office by building a real-world Task Pane App. We’ll look at using some of the more useful features of the Office API as well as review some gotchas to keep in mind while developing Task Pane Apps.
For this demo we’ll create an app called Reuser. This app will allow you to save content from a Word document into a bank so that you can reuse it later. This is helpful if you are writing something that makes constant references to a specific link, block of text, or formatted content.
The purpose of this sample is to demonstrate how you can effectively read and write content to and from the document in a useful way, as well as demonstrate how to persist content with the document so it is available for later reuse. We’ll also look at some useful API features and tips to help you create more interactive apps.
Since we've already discussed how to get started building Apps for Office, let’s dive right into the code.
Create the Project
For this demo we’ll create a new Task Pane App and choose only Word, since this app is specifically reusing content in a Word document.
We’ll keep most of the generated sample control, including the default layout and notification window that is generated during initialization.
To keep things simple, the app will have a button to store the selected content, and an unordered-list to display the saved content for reuse, updating the list via jQuery as new items are added.
Since all the behavior is being handled via JavaScript, we only need to add some simple markup to the home page:
<div id="content-main">
<div class="padding">
<p><strong>My Saved Content</strong></p>
<button id="storeContent">Save Content</button>
<ul id="list"></ul>
</div>
</div>
Note: for the CSS used to style the page, see the final source code below.
With our layout complete we can proceed to wire up the behavior using JavaScript.
Reading Content from the Document
The behavior for this page is defined in Home.js, and any initialization should take place within the provided Office.initialize method, as this is fired only after both Office and your application are fully loaded.
We need to wire up the click handlers for both of the button (to save content) and the list (to write the saved content back to the document). Here’s what we have so far:
Office.initialize = function (reason) {
$(document).ready(function () {
app.initialize();
$('#storeContent').click(getContentAsText);
var listItem = $("#list");
listItem.on("click", "li", writeContentToSelection);
});
};
function getContentAsText() {
}
function writeContentToDocument() {
}
Our first thought might be to use the default getSelectedDataAsync method from our “Hello, World” example to save the selected text. While this is valid, and useful, the default behavior of this method is to save the content as plain text. This limits our application as we would not be able to store formatted content such as links, bold text, emphasis, and other rich content.
Instead, we want to specify the type of text to retrieve from the document using the Office API support for Coercion.
Coercion Types
Office supports a wide variety of content, including text, rich text, as well as more complex content such as Tables and Open Office XML (OOXML). When selecting content via the Office API, you must indicate how the API should treat the content. This is done by specifying the CoercionType as the first parameter of the getSelectedDataAsync method.
Several types are available, including Text, Html, Table, Matrix, SlideRange (for PowerPoint) and OOXML. This property is available when both reading from, and writing to, an Office document via the API to ensure that your content is processed properly.
However, not all documents support all CoercionTypes. For example, the Html and OOXML types are available only in Word, and the SlideRange is only useable within PowerPoint. I recommend reviewing the documentation to see the latest features and support: CoercionType enumeration.
For our application, we want to preserve the formatting of the selected text, but we also want to be able to show a preview of that content when hovered over by the mouse. The Html CoercionType should suit us nicely. Simply grabbing that content as Html is going to be problematic, because we also need a “friendly name” to display in the list. Since the Html CoercionType includes a full HTML page of content, we can’t easily parse this to get the text.
Instead, we’ll have to retrieve the selected content twice, once as Text to get the friendly name, then again as Html to get the actual content to be saved.
Asynchronous Programming and AsyncContext
Recall that the majority of the methods in the Office API are asynchronous so that your application does not impact the user experience in the document. This means we can’t sequentially get the content as Text, then immediately grab the content again as Html. Instead, we need to chain the behavior via the callbacks, passing the text along so that both the plain and rich texts can be saved as a single object.
Fortunately, the getSelectedDataAsync method accepts an optional parameter object to specify custom settings, which includes a property named asyncContent. This can be a user-defined item of any type that is passed to the method and returned unmodified with the resulting callback.
We’ll use this to our advantage by first grabbing the selected content as Text, then passing it into the call to retrieve the content as Html, finally saving them to the list:
function getContentAsText() {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
if (result.value)
getContentAsHTML(result.value);
} else {
app.showNotification('Error:', result.error.message);
}
}
);
}
// Reads data from current document selection and displays a notification
function getContentAsHTML(text) {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Html, { asyncContext: text },
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
addToList(result.asyncContext, result.value);
} else {
app.showNotification('Error:', result.error.message);
}
}
);
}
function addToList(text, value) {
var $list = $("#list");
$('<li></li>')
.text(text)
.data("value", value)
.appendTo($list);
}
The addToList function then combines the two properties, setting one as the text of the list item, with the content stored as a new data “value” property for later retrieval.
Taking a look at the app in action reveals that the content saved as plain text. We’ll see next how to retrieve the saved content as fully formatted rich text and write it back to the document.
Writing Content to the Document
This part of the project is quite simple, as we already discussed the CoercionType, and simply need to make sure we use the same type when calling setSelectedDataAsync. We just need to retrieve the saved content from the “value” data property of the selected list item.
// write the selected content to the document
function writeContentToSelection(e) {
var selectedItem = $(e.target);
var content = selectedItem.data("value");
Office.context.document.setSelectedDataAsync(content, { coercionType: Office.CoercionType.Html });
}
Keep in mind that this will write the specified content at the cursor (if no content is selected) or over any selected content.
As you see in this screenshot, we indeed get back exactly the same content we stored, with all the rich formatting we expected.
As an added bonus — since the content is, after all, Html — we are able to add a tooltip preview so the user can see the formatted text before they select it.
For the full implementation of this helpful feature, be sure to grab the source code below.
Persisting Content with Document Settings
If we were to save, close, then reopen the current document, you’ll notice that while the app (which embeds into and travels with the document) automatically relaunches, all of our saved content is now gone.
This makes sense, as we haven’t done anything to persist the data. Fortunately, the Office API makes this simple by exposing a Settings property on the document, the contents of which are also embedded and travel with the document.
The Settings object has both a get and set method for storing and retrieving content, which can be a string, number, date, or object (but not a function). However, in addition to using set to store a value, if you want to save the content to the document for later reuse, you must also call the saveAsync method.
To add support for storing the content to our app, we simply created a global variable, which we use to push new items as they are added. We then save that updated content to the settings and finally store it with the document:
var content;
// write the selected item to the content object and save to the document settings
function saveContentToSettings(result) {
content.push({ text: result.asyncContext, value: result.value });
Office.context.document.settings.set("content", content);
Office.context.document.settings.saveAsync();
}
Now when we launch the app, we simply check for the presence of this setting, and if populated, reload the entries, or otherwise setting the content variable as an empty array ready for new items to be saved.
// restore content from settings
content = Office.context.document.settings.get("content");
if (content && content.length) {
$.each(content, function (i, e) {
addToList(e.text, e.value);
});
} else {
content = [];
}
However, if we were to run the app, add content, save the document and relaunch it, once again we see that the content list is empty.
The reason for this is that by default, as the document launches a brand new instance of Word with a fresh, blank document. If instead, while the app is running, we open the previously saved document, we will see that our content was stored and is ready to be reused.
Once the app is deployed live, it can even be opened via Office Online and both the app and the saved content will be available!
Wrapping Up and Next Steps
By leveraging the Office API and some simple JavaScript, we were able to create a useful, real-world app with only a few lines of code. The Task Pane App is great for sidebar-type apps like this, with a useful panel in constant view, offering interactivity and contextual information to the document.
To run the full sample for yourself, be sure to grab a copy of the project here: Download source code for Reuser Task Pane App for Office.
Now that we've seen how simple but useful the Task Pane App is for Word, we’ll next take a look at building a Content App for Excel, and later on see how simple it is to extend Outlook with a Mail App.