title | description | ms.date | ms.prod | ms.localizationpriority |
---|---|---|---|---|
Excel add-in tutorial |
Build an Excel add-in that creates, populates, filters, and sorts a table, creates a chart, freezes a table header, protects a worksheet, and opens a dialog. |
01/11/2023 |
excel |
high |
Tutorial: Create an Excel task pane add-in
In this tutorial, you’ll create an Excel task pane add-in that:
[!div class=»checklist»]
- Creates a table
- Filters and sorts a table
- Creates a chart
- Freezes a table header
- Protects a worksheet
- Opens a dialog
[!TIP]
If you’ve already completed the Build an Excel task pane add-in quick start using the Yeoman generator, and want to use that project as a starting point for this tutorial, go directly to the Create a table section to start this tutorial.If you want a completed version of this tutorial, head over to the Office Add-ins samples repo on GitHub.
Prerequisites
[!includeYeoman generator prerequisites]
-
Office connected to a Microsoft 365 subscription (including Office on the web).
[!NOTE]
If you don’t already have Office, you can join the Microsoft 365 developer program to get a free, 90-day renewable Microsoft 365 subscription to use during development.
Create your add-in project
[!includeYeoman generator create project guidance]
- Choose a project type:
Office Add-in Task Pane project
- Choose a script type:
JavaScript
- What do you want to name your add-in?
My Office Add-in
- Which Office client application would you like to support?
Excel
After you complete the wizard, the generator creates the project and installs supporting Node components. You may need to manually run npm install
in the root folder of your project if something fails during the initial setup.
[!includeYeoman generator next steps]
Create a table
In this step of the tutorial, you’ll programmatically test that your add-in supports the user’s current version of Excel, add a table to a worksheet, populate the table with data, and format it.
Code the add-in
-
Open the project in your code editor.
-
Open the file ./src/taskpane/taskpane.html. This file contains the HTML markup for the task pane.
-
Locate the
<main>
element and delete all lines that appear after the opening<main>
tag and before the closing</main>
tag. -
Add the following markup immediately after the opening
<main>
tag.<button class="ms-Button" id="create-table">Create Table</button><br/><br/>
-
Open the file ./src/taskpane/taskpane.js. This file contains the Office JavaScript API code that facilitates interaction between the task pane and the Office client application.
-
Remove all references to the
run
button and therun()
function by doing the following:-
Locate and delete the line
document.getElementById("run").onclick = run;
. -
Locate and delete the entire
run()
function.
-
-
Within the
Office.onReady
function call, locate the lineif (info.host === Office.HostType.Excel) {
and add the following code immediately after that line. Note:- This code adds an event handler for the
create-table
button. - The
createTable
function is wrapped in a call totryCatch
(both functions will be added next step). This allows any errors generated by the Office JavaScript layer to be handled separate from your service code.
// Assign event handlers and other initialization logic. document.getElementById("create-table").onclick = () => tryCatch(createTable);
- This code adds an event handler for the
-
Add the following functions to the end of the file. Note:
-
Your Excel.js business logic will be added to the function that is passed to
Excel.run
. This logic does not execute immediately. Instead, it is added to a queue of pending commands. -
The
context.sync
method sends all queued commands to Excel for execution. -
The
tryCatch
function will be used by all the functions interacting with the workbook from the task pane. Catching Office JavaScript errors in this fashion is a convenient way to generically handle any uncaught errors.
[!includeInformation about the use of ES6 JavaScript]
async function createTable() { await Excel.run(async (context) => { // TODO1: Queue table creation logic here. // TODO2: Queue commands to populate the table with data. // TODO3: Queue commands to format the table. await context.sync(); }); } /** Default helper for invoking an action and handling errors. */ async function tryCatch(callback) { try { await callback(); } catch (error) { // Note: In a production add-in, you'd want to notify the user through your add-in's UI. console.error(error); } }
-
-
Within the
createTable()
function, replaceTODO1
with the following code. Note:-
The code creates a table by using the
add
method of a worksheet’s table collection, which always exists even if it is empty. This is the standard way that Excel.js objects are created. There are no class constructor APIs, and you never use anew
operator to create an Excel object. Instead, you add to a parent collection object. -
The first parameter of the
add
method is the range of only the top row of the table, not the entire range the table will ultimately use. This is because when the add-in populates the data rows (in the next step), it will add new rows to the table instead of writing values to the cells of existing rows. This is a common pattern, because the number of rows a table will have is often unknown when the table is created. -
Table names must be unique across the entire workbook, not just the worksheet.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); const expensesTable = currentWorksheet.tables.add("A1:D1", true /*hasHeaders*/); expensesTable.name = "ExpensesTable";
-
-
Within the
createTable()
function, replaceTODO2
with the following code. Note:-
The cell values of a range are set with an array of arrays.
-
New rows are created in a table by calling the
add
method of the table’s row collection. You can add multiple rows in a single call ofadd
by including multiple cell value arrays in the parent array that is passed as the second parameter.
expensesTable.getHeaderRowRange().values = [["Date", "Merchant", "Category", "Amount"]]; expensesTable.rows.add(null /*add at the end*/, [ ["1/1/2017", "The Phone Company", "Communications", "120"], ["1/2/2017", "Northwind Electric Cars", "Transportation", "142.33"], ["1/5/2017", "Best For You Organics Company", "Groceries", "27.9"], ["1/10/2017", "Coho Vineyard", "Restaurant", "33"], ["1/11/2017", "Bellows College", "Education", "350.1"], ["1/15/2017", "Trey Research", "Other", "135"], ["1/15/2017", "Best For You Organics Company", "Groceries", "97.88"] ]);
-
-
Within the
createTable()
function, replaceTODO3
with the following code. Note:-
The code gets a reference to the Amount column by passing its zero-based index to the
getItemAt
method of the table’s column collection.[!NOTE]
Excel.js collection objects, such asTableCollection
,WorksheetCollection
, andTableColumnCollection
have anitems
property that is an array of the child object types, such asTable
orWorksheet
orTableColumn
; but a*Collection
object is not itself an array. -
The code then formats the range of the Amount column as Euros to the second decimal. Learn more about the Excel number format syntax in the article Number format codes/
-
Finally, it ensures that the width of the columns and height of the rows is big enough to fit the longest (or tallest) data item. Notice that the code must get
Range
objects to format.TableColumn
andTableRow
objects do not have format properties.
expensesTable.columns.getItemAt(3).getRange().numberFormat = [['u20AC#,##0.00']]; expensesTable.getRange().format.autofitColumns(); expensesTable.getRange().format.autofitRows();
-
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
Complete the following steps to start the local web server and sideload your add-in.
[!INCLUDE alert use https]
[!TIP]
If you’re testing your add-in on Mac, run the following command in the root directory of your project before proceeding. When you run this command, the local web server starts.-
To test your add-in in Excel, run the following command in the root directory of your project. This starts the local web server (if it’s not already running) and opens Excel with your add-in loaded.
-
To test your add-in in Excel on the web, run the following command in the root directory of your project. When you run this command, the local web server starts. Replace «{url}» with the URL of an Excel document on your OneDrive or a SharePoint library to which you have permissions.
[!INCLUDE npm start:web command syntax]
-
-
In Excel, choose the Home tab, and then choose the Show Taskpane button in the ribbon to open the add-in task pane.
-
In the task pane, choose the Create Table button.
Filter and sort a table
In this step of the tutorial, you’ll filter and sort the table that you created previously.
Filter the table
-
Open the file ./src/taskpane/taskpane.html.
-
Locate the
<button>
element for thecreate-table
button, and add the following markup after that line.<button class="ms-Button" id="filter-table">Filter Table</button><br/><br/>
-
Open the file ./src/taskpane/taskpane.js.
-
Within the
Office.onReady
function call, locate the line that assigns a click handler to thecreate-table
button, and add the following code after that line.document.getElementById("filter-table").onclick = () => tryCatch(filterTable);
-
Add the following function to the end of the file.
async function filterTable() { await Excel.run(async (context) => { // TODO1: Queue commands to filter out all expense categories except // Groceries and Education. await context.sync(); }); }
-
Within the
filterTable()
function, replaceTODO1
with the following code. Note:-
The code first gets a reference to the column that needs filtering by passing the column name to the
getItem
method, instead of passing its index to thegetItemAt
method as thecreateTable
method does. Since users can move table columns, the column at a given index might change after the table is created. Hence, it is safer to use the column name to get a reference to the column. We usedgetItemAt
safely in the preceding tutorial, because we used it in the very same method that creates the table, so there is no chance that a user has moved the column. -
The
applyValuesFilter
method is one of several filtering methods on theFilter
object.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); const expensesTable = currentWorksheet.tables.getItem('ExpensesTable'); const categoryFilter = expensesTable.columns.getItem('Category').filter; categoryFilter.applyValuesFilter(['Education', 'Groceries']);
-
Sort the table
-
Open the file ./src/taskpane/taskpane.html.
-
Locate the
<button>
element for thefilter-table
button, and add the following markup after that line.<button class="ms-Button" id="sort-table">Sort Table</button><br/><br/>
-
Open the file ./src/taskpane/taskpane.js.
-
Within the
Office.onReady
function call, locate the line that assigns a click handler to thefilter-table
button, and add the following code after that line.document.getElementById("sort-table").onclick = () => tryCatch(sortTable);
-
Add the following function to the end of the file.
async function sortTable() { await Excel.run(async (context) => { // TODO1: Queue commands to sort the table by Merchant name. await context.sync(); }); }
-
Within the
sortTable()
function, replaceTODO1
with the following code. Note:-
The code creates an array of
SortField
objects, which has just one member since the add-in only sorts on the Merchant column. -
The
key
property of aSortField
object is the zero-based index of the column used for sorting. The rows of the table are sorted based on the values in the referenced column. -
The
sort
member of aTable
is aTableSort
object, not a method. TheSortField
s are passed to theTableSort
object’sapply
method.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); const expensesTable = currentWorksheet.tables.getItem('ExpensesTable'); const sortFields = [ { key: 1, // Merchant column ascending: false, } ]; expensesTable.sort.apply(sortFields);
-
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
[!includeStart server and sideload add-in instructions]
-
If the add-in task pane isn’t already open in Excel, go to the Home tab and choose the Show Taskpane button in the ribbon to open it.
-
If the table you added previously in this tutorial is not present in the open worksheet, choose the Create Table button in the task pane.
-
Choose the Filter Table button and the Sort Table button, in either order.
Create a chart
In this step of the tutorial, you’ll create a chart using data from the table that you created previously, and then format the chart.
Chart a chart using table data
-
Open the file ./src/taskpane/taskpane.html.
-
Locate the
<button>
element for thesort-table
button, and add the following markup after that line.<button class="ms-Button" id="create-chart">Create Chart</button><br/><br/>
-
Open the file ./src/taskpane/taskpane.js.
-
Within the
Office.onReady
function call, locate the line that assigns a click handler to thesort-table
button, and add the following code after that line.document.getElementById("create-chart").onclick = () => tryCatch(createChart);
-
Add the following function to the end of the file.
async function createChart() { await Excel.run(async (context) => { // TODO1: Queue commands to get the range of data to be charted. // TODO2: Queue command to create the chart and define its type. // TODO3: Queue commands to position and format the chart. await context.sync(); }); }
-
Within the
createChart()
function, replaceTODO1
with the following code. Note that in order to exclude the header row, the code uses theTable.getDataBodyRange
method to get the range of data you want to chart instead of thegetRange
method.const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); const expensesTable = currentWorksheet.tables.getItem('ExpensesTable'); const dataRange = expensesTable.getDataBodyRange();
-
Within the
createChart()
function, replaceTODO2
with the following code. Note the following parameters.-
The first parameter to the
add
method specifies the type of chart. There are several dozen types. -
The second parameter specifies the range of data to include in the chart.
-
The third parameter determines whether a series of data points from the table should be charted row-wise or column-wise. The option
auto
tells Excel to decide the best method.
const chart = currentWorksheet.charts.add('ColumnClustered', dataRange, 'Auto');
-
-
Within the
createChart()
function, replaceTODO3
with the following code. Most of this code is self-explanatory. Note:-
The parameters to the
setPosition
method specify the upper left and lower right cells of the worksheet area that should contain the chart. Excel can adjust things like line width to make the chart look good in the space it has been given. -
A «series» is a set of data points from a column of the table. Since there is only one non-string column in the table, Excel infers that the column is the only column of data points to chart. It interprets the other columns as chart labels. So there will be just one series in the chart and it will have index 0. This is the one to label with «Value in €».
chart.setPosition("A15", "F30"); chart.title.text = "Expenses"; chart.legend.position = "Right"; chart.legend.format.fill.setSolidColor("white"); chart.dataLabels.format.font.size = 15; chart.dataLabels.format.font.color = "black"; chart.series.getItemAt(0).name = 'Value in u20AC';
-
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
[!includeStart server and sideload add-in instructions]
-
If the add-in task pane isn’t already open in Excel, go to the Home tab and choose the Show Taskpane button in the ribbon to open it.
-
If the table you added previously in this tutorial is not present in the open worksheet, choose the Create Table button, and then the Filter Table button and the Sort Table button, in either order.
-
Choose the Create Chart button. A chart is created and only the data from the rows that have been filtered are included. The labels on the data points across the bottom are in the sort order of the chart; that is, merchant names in reverse alphabetical order.
Freeze a table header
When a table is long enough that a user must scroll to see some rows, the header row can scroll out of sight. In this step of the tutorial, you’ll freeze the header row of the table that you created previously, so that it remains visible even as the user scrolls down the worksheet.
Freeze the table’s header row
-
Open the file ./src/taskpane/taskpane.html.
-
Locate the
<button>
element for thecreate-chart
button, and add the following markup after that line.<button class="ms-Button" id="freeze-header">Freeze Header</button><br/><br/>
-
Open the file ./src/taskpane/taskpane.js.
-
Within the
Office.onReady
function call, locate the line that assigns a click handler to thecreate-chart
button, and add the following code after that line.document.getElementById("freeze-header").onclick = () => tryCatch(freezeHeader);
-
Add the following function to the end of the file.
async function freezeHeader() { await Excel.run(async (context) => { // TODO1: Queue commands to keep the header visible when the user scrolls. await context.sync(); }); }
-
Within the
freezeHeader()
function, replaceTODO1
with the following code. Note:-
The
Worksheet.freezePanes
collection is a set of panes in the worksheet that are pinned, or frozen, in place when the worksheet is scrolled. -
The
freezeRows
method takes as a parameter the number of rows, from the top, that are to be pinned in place. We pass1
to pin the first row in place.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet(); currentWorksheet.freezePanes.freezeRows(1);
-
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
[!includeStart server and sideload add-in instructions]
-
If the add-in task pane isn’t already open in Excel, go to the Home tab and choose the Show Taskpane button in the ribbon to open it.
-
If the table you added previously in this tutorial is present in the worksheet, delete it.
-
In the task pane, choose the Create Table button.
-
In the task pane, choose the Freeze Header button.
-
Scroll down the worksheet far enough to see that the table header remains visible at the top even when the higher rows scroll out of sight.
Protect a worksheet
In this step of the tutorial, you’ll add a button to the ribbon that toggles worksheet protection on and off.
Configure the manifest to add a second ribbon button
-
Open the manifest file ./manifest.xml.
-
Locate the <Control> element. This element defines the Show Taskpane button on the Home ribbon you have been using to launch the add-in. We’re going to add a second button to the same group on the Home ribbon. In between the closing </Control> tag and the closing </Group> tag, add the following markup.
<Control xsi:type="Button" id="<!--TODO1: Unique (in manifest) name for button -->"> <Label resid="<!--TODO2: Button label -->" /> <Supertip> <Title resid="<!-- TODO3: Button tool tip title -->" /> <Description resid="<!-- TODO4: Button tool tip description -->" /> </Supertip> <Icon> <bt:Image size="16" resid="Icon.16x16"/> <bt:Image size="32" resid="Icon.32x32"/> <bt:Image size="80" resid="Icon.80x80"/> </Icon> <Action xsi:type="<!-- TODO5: Specify the type of action-->"> <!-- TODO6: Identify the function.--> </Action> </Control>
-
Within the XML you just added to the manifest file, replace
TODO1
with a string that gives the button an ID that is unique within this manifest file. Since our button is going to toggle protection of the worksheet on and off, use «ToggleProtection». When you are done, the opening tag for theControl
element should look like this:<Control xsi:type="Button" id="ToggleProtection">
-
The next three
TODO
s set resource IDs, orresid
s. A resource is a string (with a maximum length of 32 characters), and you’ll create these three strings in a later step. For now, you need to give IDs to the resources. The button label should read «Toggle Protection», but the ID of this string should be «ProtectionButtonLabel», so theLabel
element should look like this:<Label resid="ProtectionButtonLabel" />
-
The
SuperTip
element defines the tool tip for the button. The tool tip title should be the same as the button label, so we use the very same resource ID: «ProtectionButtonLabel». The tool tip description will be «Click to turn protection of the worksheet on and off». But theresid
should be «ProtectionButtonToolTip». So, when you are done, theSuperTip
element should look like this:<Supertip> <Title resid="ProtectionButtonLabel" /> <Description resid="ProtectionButtonToolTip" /> </Supertip>
[!NOTE]
In a production add-in, you would not want to use the same icon for two different buttons; but to simplify this tutorial, we’ll do that. So theIcon
markup in our newControl
is just a copy of theIcon
element from the existingControl
. -
The
Action
element inside the originalControl
element has its type set toShowTaskpane
, but our new button isn’t going to open a task pane; it’s going to run a custom function that you create in a later step. So, replaceTODO5
withExecuteFunction
, which is the action type for buttons that trigger custom functions. The opening tag for theAction
element should look like this:<Action xsi:type="ExecuteFunction">
-
The original
Action
element has child elements that specify a task pane ID and a URL of the page that should be opened in the task pane. But anAction
element of theExecuteFunction
type has a single child element that names the function that the control executes. You’ll create that function in a later step, and it will be calledtoggleProtection
. So, replaceTODO6
with the following markup.<FunctionName>toggleProtection</FunctionName>
The entire
Control
markup should now look like the following:<Control xsi:type="Button" id="ToggleProtection"> <Label resid="ProtectionButtonLabel" /> <Supertip> <Title resid="ProtectionButtonLabel" /> <Description resid="ProtectionButtonToolTip" /> </Supertip> <Icon> <bt:Image size="16" resid="Icon.16x16"/> <bt:Image size="32" resid="Icon.32x32"/> <bt:Image size="80" resid="Icon.80x80"/> </Icon> <Action xsi:type="ExecuteFunction"> <FunctionName>toggleProtection</FunctionName> </Action> </Control>
-
Scroll down to the
Resources
section of the manifest. -
Add the following markup as a child of the
bt:ShortStrings
element.<bt:String id="ProtectionButtonLabel" DefaultValue="Toggle Worksheet Protection" />
-
Add the following markup as a child of the
bt:LongStrings
element.<bt:String id="ProtectionButtonToolTip" DefaultValue="Click to protect or unprotect the current worksheet." />
-
Save the file.
Create the function that protects the sheet
-
Open the file .commandscommands.js.
-
Add the following function immediately after the
action
function. Note that we specify anargs
parameter to the function and the very last line of the function callsargs.completed
. This is a requirement for all add-in commands of type ExecuteFunction. It signals the Office client application that the function has finished and the UI can become responsive again.async function toggleProtection(args) { try { await Excel.run(async (context) => { // TODO1: Queue commands to reverse the protection status of the current worksheet. await context.sync(); }); } catch (error) { // Note: In a production add-in, you'd want to notify the user through your add-in's UI. console.error(error); } args.completed(); }
-
Add the following line immediately after the function to register it.
Office.actions.associate("toggleProtection", toggleProtection);
-
Within the
toggleProtection
function, replaceTODO1
with the following code. This code uses the worksheet object’s protection property in a standard toggle pattern. TheTODO2
will be explained in the next section.const sheet = context.workbook.worksheets.getActiveWorksheet(); // TODO2: Queue command to load the sheet's "protection.protected" property from // the document and re-synchronize the document and task pane. if (sheet.protection.protected) { sheet.protection.unprotect(); } else { sheet.protection.protect(); }
Add code to fetch document properties into the task pane’s script objects
In each function that you’ve created in this tutorial until now, you queued commands to write to the Office document. Each function ended with a call to the context.sync()
method, which sends the queued commands to the document to be executed. However, the code you added in the last step calls the sheet.protection.protected property
. This is a significant difference from the earlier functions you wrote, because the sheet
object is only a proxy object that exists in your task pane’s script. The proxy object doesn’t know the actual protection state of the document, so its protection.protected
property can’t have a real value. To avoid an exception error, you must first fetch the protection status from the document and use it set the value of sheet.protection.protected
. This fetching process has three steps.
-
Queue a command to load (that is; fetch) the properties that your code needs to read.
-
Call the context object’s
sync
method to send the queued command to the document for execution and return the requested information. -
Because the
sync
method is asynchronous, ensure that it has completed before your code calls the properties that were fetched.
These steps must be completed whenever your code needs to read information from the Office document.
-
Within the
toggleProtection
function, replaceTODO2
with the following code. Note:-
Every Excel object has a
load
method. You specify the properties of the object that you want to read in the parameter as a string of comma-delimited names. In this case, the property you need to read is a subproperty of theprotection
property. You reference the subproperty almost exactly as you would anywhere else in your code, with the exception that you use a forward slash (‘/’) character instead of a «.» character. -
To ensure that the toggle logic, which reads
sheet.protection.protected
, doesn’t run until after thesync
is complete and thesheet.protection.protected
has been assigned the correct value that is fetched from the document, it must come after theawait
operator ensuressync
has completed.
sheet.load('protection/protected'); await context.sync();
When you are done, the entire function should look like the following:
async function toggleProtection(args) { try { await Excel.run(async (context) => { const sheet = context.workbook.worksheets.getActiveWorksheet(); sheet.load('protection/protected'); await context.sync(); if (sheet.protection.protected) { sheet.protection.unprotect(); } else { sheet.protection.protect(); } await context.sync(); }); } catch (error) { // Note: In a production add-in, you'd want to notify the user through your add-in's UI. console.error(error); } args.completed(); }
-
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
Close all Office applications, including Excel (or close the browser tab if you’re using Excel on the web).
-
Clear the Office cache. This is necessary to completely clear the old version of the add-in from the client application. Instructions for this process are in the article Clear the Office cache.
-
If the local web server is already running, stop it by entering the following command in the command prompt. This should close the node command window.
-
Because your manifest file has been updated, you must sideload your add-in again, using the updated manifest file. Start the local web server and sideload your add-in.
-
To test your add-in in Excel, run the following command in the root directory of your project. This starts the local web server (if it’s not already running) and opens Excel with your add-in loaded.
-
To test your add-in in Excel on the web, run the following command in the root directory of your project. When you run this command, the local web server starts. Replace «{url}» with the URL of an Excel document on your OneDrive or a SharePoint library to which you have permissions.
[!INCLUDE npm start:web command syntax]
-
-
On the Home tab in Excel, choose the Toggle Worksheet Protection button. Note that most of the controls on the ribbon are disabled (and visually grayed-out) as seen in the following screenshot.
-
Select a cell and try to edit its content. Excel displays an error message indicating that the worksheet is protected.
-
Choose the Toggle Worksheet Protection button again, and the controls are reenabled, and you can change cell values again.
Open a dialog
In this final step of the tutorial, you’ll open a dialog in your add-in, pass a message from the dialog process to the task pane process, and close the dialog. Office Add-in dialogs are nonmodal: a user can continue to interact with both the document in the Office application and with the host page in the task pane.
Create the dialog page
-
In the ./src folder that’s located at the root of the project, create a new folder named dialogs.
-
In the ./src/dialogs folder, create new file named popup.html.
-
Add the following markup to popup.html. Note:
-
The page has an
<input>
field where the user will enter their name, and a button that will send this name to the task pane where it will display. -
The markup loads a script named popup.js that you will create in a later step.
-
It also loads the Office.js library because it will be used in popup.js.
<!DOCTYPE html> <html> <head lang="en"> <title>Dialog for My Office Add-in</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- For more information on Fluent UI, visit https://developer.microsoft.com/fluentui. --> <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css"/> <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script> <script type="text/javascript" src="popup.js"></script> </head> <body style="display:flex;flex-direction:column;align-items:center;justify-content:center"> <p class="ms-font-xl">ENTER YOUR NAME</p> <input id="name-box" type="text"/><br/><br/> <button id="ok-button" class="ms-Button">OK</button> </body> </html>
-
-
In the ./src/dialogs folder, create new file named popup.js.
-
Add the following code to popup.js. Note the following about this code.
- Every page that calls APIs in the Office.js library must first ensure that the library is fully initialized. The best way to do that is to call the
Office.onReady()
function. The call ofOffice.onReady()
must run before any calls to Office.js; hence the assignment is in a script file that is loaded by the page, as it is in this case.
Office.onReady((info) => { // TODO1: Assign handler to the OK button. }); // TODO2: Create the OK button handler.
- Every page that calls APIs in the Office.js library must first ensure that the library is fully initialized. The best way to do that is to call the
-
Replace
TODO1
with the following code. You’ll create thesendStringToParentPage
function in the next step.document.getElementById("ok-button").onclick = () => tryCatch(sendStringToParentPage);
-
Replace
TODO2
with the following code. ThemessageParent
method passes its parameter to the parent page, in this case, the page in the task pane. The parameter must be a string, which includes anything that can be serialized as a string, such as XML or JSON, or any type that can be cast to a string. This also adds the sametryCatch
method used in taskpane.js for error handling.function sendStringToParentPage() { const userName = document.getElementById("name-box").value; Office.context.ui.messageParent(userName); } /** Default helper for invoking an action and handling errors. */ async function tryCatch(callback) { try { await callback(); } catch (error) { // Note: In a production add-in, you'd want to notify the user through your add-in's UI. console.error(error); } }
[!NOTE]
The popup.html file, and the popup.js file that it loads, run in an entirely separate browser runtime process from the add-in’s task pane. If popup.js was transpiled into the same bundle.js file as the app.js file, then the add-in would have to load two copies of the bundle.js file, which defeats the purpose of bundling. Therefore, this add-in does not transpile the popup.js file at all.
Update webpack config settings
Open the file webpack.config.js in the root directory of the project and complete the following steps.
-
Locate the
entry
object within theconfig
object and add a new entry forpopup
.popup: "./src/dialogs/popup.js"
After you’ve done this, the new
entry
object will look like this.entry: { polyfill: "@babel/polyfill", taskpane: "./src/taskpane/taskpane.js", commands: "./src/commands/commands.js", popup: "./src/dialogs/popup.js" },
-
Locate the
plugins
array within theconfig
object and add the following object to the end of that array.new HtmlWebpackPlugin({ filename: "popup.html", template: "./src/dialogs/popup.html", chunks: ["polyfill", "popup"] })
After you’ve done this, the new
plugins
array will look like this.plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ filename: "taskpane.html", template: "./src/taskpane/taskpane.html", chunks: ['polyfill', 'taskpane'] }), new CopyWebpackPlugin([ { to: "taskpane.css", from: "./src/taskpane/taskpane.css" } ]), new HtmlWebpackPlugin({ filename: "commands.html", template: "./src/commands/commands.html", chunks: ["polyfill", "commands"] }), new HtmlWebpackPlugin({ filename: "popup.html", template: "./src/dialogs/popup.html", chunks: ["polyfill", "popup"] }) ],
-
If the local web server is running, stop it by entering the following command in the command prompt. This should close the node command window.
-
Run the following command to rebuild the project.
Open the dialog from the task pane
-
Open the file ./src/taskpane/taskpane.html.
-
Locate the
<button>
element for thefreeze-header
button, and add the following markup after that line.<button class="ms-Button" id="open-dialog">Open Dialog</button><br/><br/>
-
The dialog will prompt the user to enter a name and pass the user’s name to the task pane. The task pane will display it in a label. Immediately after the
button
that you just added, add the following markup.<label id="user-name"></label><br/><br/>
-
Open the file ./src/taskpane/taskpane.js.
-
Within the
Office.onReady
function call, locate the line that assigns a click handler to thefreeze-header
button, and add the following code after that line. You’ll create theopenDialog
method in a later step.document.getElementById("open-dialog").onclick = openDialog;
-
Add the following declaration to the end of the file. This variable is used to hold an object in the parent page’s execution context that acts as an intermediator to the dialog page’s execution context.
-
Add the following function to the end of the file (after the declaration of
dialog
). The important thing to notice about this code is what is not there: there is no call ofExcel.run
. This is because the API to open a dialog is shared among all Office applications, so it is part of the Office JavaScript Common API, not the Excel-specific API.function openDialog() { // TODO1: Call the Office Common API that opens a dialog. }
-
Replace
TODO1
with the following code. Note:-
The
displayDialogAsync
method opens a dialog in the center of the screen. -
The first parameter is the URL of the page to open.
-
The second parameter passes options.
height
andwidth
are percentages of the size of the Office application’s window.
Office.context.ui.displayDialogAsync( 'https://localhost:3000/popup.html', {height: 45, width: 55}, // TODO2: Add callback parameter. );
-
Process the message from the dialog and close the dialog
-
Within the
openDialog
function in the file ./src/taskpane/taskpane.js, replaceTODO2
with the following code. Note:-
The callback is executed immediately after the dialog successfully opens and before the user has taken any action in the dialog.
-
The
result.value
is the object that acts as an intermediary between the execution contexts of the parent and dialog pages. -
The
processMessage
function will be created in a later step. This handler will process any values that are sent from the dialog page with calls of themessageParent
function.
function (result) { dialog = result.value; dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage); }
-
-
Add the following function after the
openDialog
function.function processMessage(arg) { document.getElementById("user-name").innerHTML = arg.message; dialog.close(); }
-
Verify that you’ve saved all of the changes you’ve made to the project.
Test the add-in
-
[!includeStart server and sideload add-in instructions]
-
If the add-in task pane isn’t already open in Excel, go to the Home tab and choose the Show Taskpane button in the ribbon to open it.
-
Choose the Open Dialog button in the task pane.
-
While the dialog is open, drag it and resize it. Note that you can interact with the worksheet and press other buttons on the task pane, but you cannot launch a second dialog from the same task pane page.
-
In the dialog, enter a name and choose the OK button. The name appears on the task pane and the dialog closes.
-
Optionally, in the ./src/taskpane/taskpane.js file, comment out the line
dialog.close();
in theprocessMessage
function. Then repeat the steps of this section. The dialog stays open and you can change the name. You can close it manually by pressing the X button in the upper right corner.
Next steps
In this tutorial, you’ve created an Excel task pane add-in that interacts with tables, charts, worksheets, and dialogs in an Excel workbook. To learn more about building Excel add-ins, continue to the following article.
[!div class=»nextstepaction»]
Excel add-ins overview
See also
- Office Add-ins platform overview
- Develop Office Add-ins
- Excel JavaScript object model in Office Add-ins
- Office Add-ins code samples
What is an Excel Add-in?
MS Excel Add-in is a kind of program or a utility that lets you perform fundamental processes more quickly. It does this by integrating new features into the excel application that boosts its basic capabilities on various platforms like Windows, Mac & Web.
The Excel Add-in, as part of the Office platform, allows you to modify and speed up your business processes. Office Add-ins are well-known for their centralized deployment, cross-platform compatibility, and AppSource distribution. It enables developers to leverage web technologies including HTML, CSS, and JavaScript.
More importantly, it provides the framework and the JavaScript library Office.js for constructing Excel Add-ins. In this tutorial, we will walk through the basic yet effective process of creating the Excel Addin using ReactJS.
Prerequisites for setting up your development environment
Before you start creating Excel Add-ins, make sure you have these prerequisites installed on your PC.
- NPM
- Node.js
- Visual Studio
- A Microsoft 365 account with a subscription
Looking for the best Excel Add-in development company ? Connect us now.
How to build Excel Add-in using React
To begin, configure and install the Yeoman and Yeoman generator for Office 365 Add-in development.
npm install -g yo generator-office
Enter fullscreen mode
Exit fullscreen mode
Now run the following yo command to create an Add-in
yo office
Enter fullscreen mode
Exit fullscreen mode
After running the above command, select the Project type as a React framework. Take a look at the reference image below.
After selecting the project, choose TypeScript as your script type.
Now, name your Excel Add-in project as shown below. You can give whatever name you like but giving a project-relevant name would be an ideal move.
Read More: React Element vs Component: A deep dive into differences
Because it is critical to provide support for the office application, choose Excel as the Office client.
Congratulations!! Your first Excel Add-in is created successfully.
How to run Excel Add-in?
Add-ins are not instantly accessible in Excel by default. We must activate them before we may use them. Let’s have a look at how to use a command prompt to execute Add-ins in MS Excel.
Use the following command and open the project folder on the command prompt.
cd Excel_Tutorial
Enter fullscreen mode
Exit fullscreen mode
Now start the dev-server as shown below.
npm run dev-server
Enter fullscreen mode
Exit fullscreen mode
To test Add-in in your Excel, run the following command in the project’s root directory.
npm start
Enter fullscreen mode
Exit fullscreen mode
When you complete running this command, you should see a task pane added to Excel that operates like an Excel Add in.
How to create a Table using ReactJS?
Businesses commonly use tables to present their business data whether it be price, comparison, financial comparison, etc. React.js makes it simple and quick for organizations to manage large amounts of data. Let’s understand the process of creating a table using React.js.
To begin,
Planning to hire dedicated ReactJS developers? Contact us now.
- Open the project in VS code
- Open the file which is located in srctaskpanecomponentsapp.tsx
- Remove the componentDidMount() method and click() method from app.tsx
- Remove all tags which are inside the return method and add one button inside the return method to generate a table
5. App.tsx
import * as React from "react";
import Progress from "./Progress";
export interface AppProps {
title: string;
isOfficeInitialized: boolean;
}
export default class App extends React.Component<appprops> {
constructor(props, context) {
super(props, context);
this.state = {
listItems: [],
};
}
render() {
const { title, isOfficeInitialized } = this.props;
if (!isOfficeInitialized) {
return (
);
}
return (
<>
<button>Generate Table</button>
);
}
}
</appprops>
Enter fullscreen mode
Exit fullscreen mode
Create one event handler function for the button which will contain the logic for creating a new table.
<appprops><button onclick="{this.handleCreateTable}">Generate Table</button>
</appprops>
Enter fullscreen mode
Exit fullscreen mode
Excel.js business logic will be added to the handleCreateTable function that is passed to Excel.run method.
The context.sync method sends all pending commands which are in queue to Excel for execution.
The Excel.run method is followed by the catch block.
handleCreateTable = async () => {
await Excel.run(async (context) => {
// logic for create table
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
In Excel.run method, first we have to get the current worksheet, and to do so, use the following method.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
Enter fullscreen mode
Exit fullscreen mode
Once we get the worksheet, we’ll create a table. Use the following method to create a table.
const salaryTable = currentWorksheet.tables.add("A1:D1", true);
Enter fullscreen mode
Exit fullscreen mode
The table is generated by using the add() function on the table collection of the current worksheet. The method accepts the first parameter as a range of the top row of the table.
We can also give a name to our table as shown below.
salaryTable.name = "SalaryTable";
Enter fullscreen mode
Exit fullscreen mode
Now, add a header row using the code shown below.
salaryTable.getHeaderRowRange().values =
[["Name", "Occupation", "Age","Salary"]];
Enter fullscreen mode
Exit fullscreen mode
The table’s rows are then inserted using the add() function of the table’s row collection. We may add several rows in a single request by sending an array of cell values within the parent array.
salaryTable.rows.add(null /*add at the end*/, [
["Poojan", "Software Developer","39", "50,000"],
["Meera", "Fashion Designer","23", "30,000"],
["Smit", "Teacher", "25","35,000"],
["Kashyap", "Scientist", "29","70,000"],
["Neha", "Teacher","34", "15,000"],
["Jay", "DevOps Developer","31", "65,000"]
]);
Enter fullscreen mode
Exit fullscreen mode
We can change the format of salary to decimal. For that, we have to pass the column zero-based index to the getItemAt() method.
salaryTable.columns.getItemAt(3).getRange().numberFormat = [['##0.00']];
Enter fullscreen mode
Exit fullscreen mode
When we use the table to represent business data, it is important to ensure content is displayed clearly. With the fine use of the autofitColumns() and autofitRows() methods, we can perfectly fit the content into cells.
salaryTable.getRange().format.autofitColumns();
salaryTable.getRange().format.autofitRows();
Enter fullscreen mode
Exit fullscreen mode
Read More: Flutter vs. React Native: Choose the Best for your App in 2022
Let’s take a look at how the entire function appears to be.
handleCreateTable = async () => {
await Excel.run(async (context) => {
const currentWorksheet=context.workbook.worksheets.getActiveWorksheet();
const salaryTable = currentWorksheet.tables.add("A1:D1", true );
salaryTable.name = "SalaryTable";
salaryTable.getHeaderRowRange().values =
[["Name", "Occupation", "Age", "Salary"]];
salaryTable.rows.add(null /*add at the end*/, [
["Poojan", "Software Developer", "39", "50,000"],
["Meera", "Fashion Designer", "23", "30,000"],
["Smit", "Teacher", "25", "35,000"],
["Kashyap", "Scientist", "29", "70,000"],
["Neha", "Teacher", "34", "15,000"],
["Jay", "DevOps Developer", "31", "65,000"]
]);
salaryTable.columns.getItemAt(3).getRange().numberFormat = [['##0.00']];
salaryTable.getRange().format.autofitColumns();
salaryTable.getRange().format.autofitRows();
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
Now, use the npm start command to run the code. That’s all there is to it; now, when the user hits the generate table button, he’ll see the following result.
Output:
How to Filter data in a table?
Filtering data is critical because organizations utilize it to exclude undesired results for analysis. Let’s see how data in a table may be filtered for better analysis.
<button>Filter Data</button>
Enter fullscreen mode
Exit fullscreen mode
<button onclick="{this.filterData}">Filter Data</button>
Enter fullscreen mode
Exit fullscreen mode
- Open the file which is located in srctaskpanecomponentsapp.tsx
- Add a new button for filter data below Generate Table button.
- Create one event handler function for the button that will contain the filter data logic.
4. filterData function:
filterData = async () => {
await Excel.run(async (context) => {
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
Then we will get the current worksheet and table.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
const salaryTable = currentWorksheet.tables.getItem('salaryTable');
Enter fullscreen mode
Exit fullscreen mode
To begin filtering data, we must first access the column from which we will be filtering data.
const occupationFilter = salaryTable.columns.getItem('Occupation').filter;
Enter fullscreen mode
Exit fullscreen mode
Here, Occupation is the column name on which we want to apply the filter.
Next, pass the values as a filter query.
occupationFilter.applyValuesFilter(['Software Developer', 'Teacher']);
Enter fullscreen mode
Exit fullscreen mode
Meanwhile, take a look at how the whole function looks like.
filterData = async () => {
await Excel.run(async (context) => {
const currentWorksheet=context.workbook.worksheets.getActiveWorksheet();
const salaryTable = currentWorksheet.tables.getItem('salaryTable');
const occupationFilter=salaryTable.columns.getItem('Occupation').filter;
occupationFilter.applyValuesFilter(['Software Developer', 'Teacher']);
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
Finally, run the code using the npm start command. Now when the user clicks on the filter data button, he’ll see the following result.
Output:
How to sort data in the table?
Data sorting is also important since it helps to obtain well-organized data in a sequential manner. Let’s understand in simple ways, how data can be sorted in a table.
To start with,
<button>Sort Data</button>
Enter fullscreen mode
Exit fullscreen mode
Searching for the best Microsoft 365 development solutions? Your search ends here.
<button onclick="{this.sortData}">Sort Data</button>
Enter fullscreen mode
Exit fullscreen mode
- Open the project in VS code
- Open the file from the path: srctaskpanecomponentsapp.tsx
- Add a new button for sorting data below the filter data button.
- Create one event handler function for the button which will contain the logic for sorting the data.
5. sortData function:
sortData=async()=>{
await Excel.run(async (context) => {
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
Let’s start by getting the current worksheet and table.
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
const salaryTable = currentWorksheet.tables.getItem('salaryTable');
Enter fullscreen mode
Exit fullscreen mode
In the function, we will build a sort field object and supply two parameters to it: the key and the type of sorting (ascending or descending).
Note:
The key property is the zero-based index of the column, and it is used for sorting. All the rows of data are sorted according to key.
const sortFields = [
{
key: 3,
ascending: false,
}
];
Enter fullscreen mode
Exit fullscreen mode
Subsequently, we use the sort and apply method on the table and pass the sortFields object.
salaryTable.sort.apply(sortFields);
Enter fullscreen mode
Exit fullscreen mode
Read More: Comparative Analysis of Blazor, Angular, React, Vue and Node for Web development
Here is what the whole function might look like. <<>
sortData = async () => {
await Excel.run(async (context) => {
const currentWorksheet=context.workbook.worksheets.getActiveWorksheet();
const salaryTable = currentWorksheet.tables.getItem('salaryTable');
const sortFields = [
{
key: 3,
ascending: false,
}
];
salaryTable.sort.apply(sortFields);
await context.sync();
}).catch((err) => {
console.log("Error: " + err);
});
}
Enter fullscreen mode
Exit fullscreen mode
Run the code using the npm start command
Finally, run the code with the npm start command. The user will see the following result every time he clicks on the sort data button.
output
Conclusion
Office Add-ins benefit businesses with faster operations and processes. In Office Add-ins, you can use familiar technologies like HTML, CSS & JavaScript to create Outlook, Excel, Word, and PowerPoint Add-ins. In this blog, we learned how to create an Excel Addin with React library from scratch and how to create tables, filter & sort data in Excel using Excel Add-in.
Recently, I was asked what is needed in order to create the most basic add-in you can. The least amount of files, work and effort. Technically, you only need two files:
- An HTML file with all your JavaScript inline and all the Office and JQuery libraries being pulled from a CDN.
- An XML Manifest file that defines the add-in.
You publish the HTML file to a web server, get the address to where it is published, update the manifest with that information and then install the XML file as an add-in. The XML file will define the add-in name/description and points to the HTML file for the code. When you launch the add-in, the task pane will open and the HTML page will be loaded in it. From there your JavaScript will execute and your add-in will come alive. It is THAT easy. 2 files.
To demonstrate this, I created a simple Excel add-in. Here is the code for the bare minimum manifest XML:
Here is the HTML file that is quite basic, I am not even using the Microsoft Fabric for the controls. This is a simple HTML page, period:
[code lang=”xml” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
<!DOCTYPE html>
<html>
<head>
<meta charset=»UTF-8″ />
<meta http-equiv=»X-UA-Compatible» content=»IE=Edge» />
<title>Excel Basic Add-In</title>
<!–using JQuery CDN so we do not need to include–>
<script src=»https://code.jquery.com/jquery-1.12.4.min.js" type=»text/javascript»></script>
<script src=»https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type=»text/javascript»></script>
<script type=»text/javascript»>
(function () {
«use strict»;
// The initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
// Add a click event handler for the button.
$(‘#simple-button’).click(function () {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
$(«#banner-text»).text(‘The selected text is: «’ + result.value + ‘»’);
$(«#banner»).show();
} else {
$(«#banner-text»).text(‘Error: ‘ + result.error.message);
$(«#banner»).show();
}
});
});
$(«#banner-close»).click(function () { $(«#banner»).hide(); });
$(«#banner»).hide();
});
}
})();
</script>
</head>
<body>
<div id=»content-main»>
<div class=»padding»>
<h1>Basic Addin</h1>
<div>Select a cell with text and then…</div>
<button id=»simple-button»>Click Here!</button></div>
</div>
<div id=»banner» style=»position:absolute;bottom: 0;»>
<div id=»banner-text»></div>
<button id=»banner-close»> <i>X</i> </button></div>
</body>
</html>
[/code]
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset=»UTF-8» /> | |
<meta http-equiv=»X-UA-Compatible» content=»IE=Edge» /> | |
<title>Excel Basic Add-In</title> | |
<!–using JQuery CDN so we do not need to include–> | |
<script src=»https://code.jquery.com/jquery-1.12.4.min.js» type=»text/javascript«></script> | |
<script src=»https://appsforoffice.microsoft.com/lib/1/hosted/office.js» type=»text/javascript«></script> | |
<script type=»text/javascript«> | |
(function () { | |
«use strict»; | |
// The initialize function must be run each time a new page is loaded. | |
Office.initialize = function (reason) { | |
$(document).ready(function () { | |
// Add a click event handler for the button. | |
$(‘#simple-button’).click(function () { | |
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text, | |
function (result) { | |
if (result.status === Office.AsyncResultStatus.Succeeded) { | |
$(«#banner-text»).text(‘The selected text is: «‘ + result.value + ‘»‘); | |
$(«#banner»).show(); | |
} else { | |
$(«#banner-text»).text(‘Error: ‘ + result.error.message); | |
$(«#banner»).show(); | |
} | |
}); | |
}); | |
$(«#banner-close»).click(function () { $(«#banner»).hide(); }); | |
$(«#banner»).hide(); | |
}); | |
} | |
})(); | |
</script> | |
</head> | |
<body> | |
<div id=»content-main«> | |
<div class=»padding«> | |
<h1>Basic Addin</h1> | |
<div>Select a cell with text and then…</div> | |
<button id=»simple-button«>Click Here!</button> | |
</div> | |
</div> | |
<div id=»banner» style=»position:absolute;bottom: 0;«> | |
<div id=»banner-text«></div> | |
<button id=»banner-close«> <i>X</i> </button> | |
</div> | |
</body> | |
</html> |
What this add-in does is very simple. It opens the task pane with a single button. Enter some text in a cell in Excel, select that cell, and then click the button. It gets the text and displays it in a popup at the bottom of the pane. I have published this add-in manifest here:
https://basicaddin.azurewebsites.net/basicaddin.xml
You can run it by using these steps:
- Open Excel Online. I first log into https://outlook.office365.com. Then I click on the menu button in the upper left and select Excel.
- I select to create a “New blank workbook.”
- On the Insert tab, I click Add-ins.
- In the upper right, I select “Upload My Add-in”
- I click Browse and in the Filename box, I put https://basicaddin.azurewebsites.net/basicaddin.xml and then click Open. This will download the add-in to the cache on your system and you will get a file name like “basicaddin[1].xml.”
- Click Upload.
- The add-in will load from my Azure site and you will see the button demonstrated above.
Now, to go ahead and answer a question I know are coming. How did I upload this Azure without Visual Studio publishing tools:
- I went to my Azure Protal (portal.azure.com).
- I clicked the +New button on the left side.
- I selected Web + Mobile, then Web App
- I filled in the required information: Name and resource group and clicked Create.
- After it was done, I followed these steps to get to the site via FTP: https://blogs.msdn.microsoft.com/kaushal/2014/08/01/microsoft-azure-web-site-connect-to-your-site-via-ftp-and-uploaddownload-files/
- I uploaded ONLY the HTML file.
- I got the path to the location from the Azure portal:
- Next, I updated the XML manifest to point to https://basicaddin.azurewebsites.net/home.html
- Then I used the steps above to load it into Excel Online.
This is fairly simple from a web developer perspective. Now, if you are not a web developer (but a traditional Office developer), you are probably still on the fence on how/why you would want to go through all of this. Again, the primary thing I am demonstrating here is how you can build an add-in with as few files as possible. But, I want to reiterate how these new add-ins are truly cross-platform. Follow these steps on your iPad and you will know what I mean. It is incredible and this work really is not that difficult to perform. It might just be a tad time consuming in the beginning as you are getting used to the new interfaces.
If you have questions, please feel free to reach out to me.
The approach you have shown uses a mixture of Spreadsheet XML and HTML. With this mixture it is not possible to fill multiple worksheets. To do this we have to use only Spreadsheet XML consequently. This is because only XML can describe multiple worksheets. The HTML table data is not related to any worksheets except the active worksheet.
To use only Spreadsheet XML is possible, but then we have to work carefully with data types. If Excel imports HTML, it tries to detect data types as if the user would enter the values into cells manually. With XML it does not so. It takes the given data types from the XML. If they do not fit, then it produces errors. So in my example, I use «data-» attríbutes to describe the data-type, data-style and data-value. So it is possible to have the data-value different from the data presentation within the HTML table cell (TD). Since HTML is a format for data presentation and not for data exchange, this is also good practice in my opinion.
For Spreadsheet XML see: http://msdn.microsoft.com/en-us/library/aa140066.aspx
The example uses data URI as download link, so it works only with browsers that support this. It will not work with Microsoft Internet Explorer.
Example:
<script type="text/javascript">
var tablesToExcel = (function() {
var uri = 'data:application/vnd.ms-excel;base64,'
, tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
+ '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Author>Axel Richter</Author><Created>{created}</Created></DocumentProperties>'
+ '<Styles>'
+ '<Style ss:ID="Currency"><NumberFormat ss:Format="Currency"></NumberFormat></Style>'
+ '<Style ss:ID="Date"><NumberFormat ss:Format="Medium Date"></NumberFormat></Style>'
+ '</Styles>'
+ '{worksheets}</Workbook>'
, tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{rows}</Table></Worksheet>'
, tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>'
, base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))) }
, format = function(s, c) { return s.replace(/{(w+)}/g, function(m, p) { return c[p]; }) }
return function(tables, wsnames, wbname, appname) {
var ctx = "";
var workbookXML = "";
var worksheetsXML = "";
var rowsXML = "";
for (var i = 0; i < tables.length; i++) {
if (!tables[i].nodeType) tables[i] = document.getElementById(tables[i]);
for (var j = 0; j < tables[i].rows.length; j++) {
rowsXML += '<Row>'
for (var k = 0; k < tables[i].rows[j].cells.length; k++) {
var dataType = tables[i].rows[j].cells[k].getAttribute("data-type");
var dataStyle = tables[i].rows[j].cells[k].getAttribute("data-style");
var dataValue = tables[i].rows[j].cells[k].getAttribute("data-value");
dataValue = (dataValue)?dataValue:tables[i].rows[j].cells[k].innerHTML;
var dataFormula = tables[i].rows[j].cells[k].getAttribute("data-formula");
dataFormula = (dataFormula)?dataFormula:(appname=='Calc' && dataType=='DateTime')?dataValue:null;
ctx = { attributeStyleID: (dataStyle=='Currency' || dataStyle=='Date')?' ss:StyleID="'+dataStyle+'"':''
, nameType: (dataType=='Number' || dataType=='DateTime' || dataType=='Boolean' || dataType=='Error')?dataType:'String'
, data: (dataFormula)?'':dataValue
, attributeFormula: (dataFormula)?' ss:Formula="'+dataFormula+'"':''
};
rowsXML += format(tmplCellXML, ctx);
}
rowsXML += '</Row>'
}
ctx = {rows: rowsXML, nameWS: wsnames[i] || 'Sheet' + i};
worksheetsXML += format(tmplWorksheetXML, ctx);
rowsXML = "";
}
ctx = {created: (new Date()).getTime(), worksheets: worksheetsXML};
workbookXML = format(tmplWorkbookXML, ctx);
console.log(workbookXML);
var link = document.createElement("A");
link.href = uri + base64(workbookXML);
link.download = wbname || 'Workbook.xls';
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
})();
</script>
<table id="tbl1">
<tr>
<td>Name</td>
<td>Birthday</td>
<td>Amount</td>
<td>Rebate (10%)</td>
</tr>
<tr>
<td>Smith</td>
<td data-type="DateTime" data-style="Date" data-value="1980-03-23">Mar 23 1980</td>
<td data-type="Number" data-style="Currency" data-value="1234.56">$ 1,234.56</td>
<td data-formula="=RC[-1]/10" data-type="Number" data-style="Currency">$ 123.45</td>
</tr>
<tr>
<td>Doe</td>
<td data-type="DateTime" data-style="Date" data-value="1978-11-05">Nov 05 1978</td>
<td data-type="Number" data-style="Currency" data-value="2345.67">$ 2,345.67</td>
<td data-formula="=RC[-1]/10" data-type="Number" data-style="Currency">$ 234.56</td>
</tr>
</table>
<hr>
<table id="tbl2">
<tr>
<td>Product</td>
<td>Price</td>
<td>Available</td>
<td>Count</td>
</tr>
<tr>
<td>Bred</td>
<td data-type="Number" data-style="Currency" data-value="1.89">$ 1.89</td>
<td data-type="Boolean" data-value="1">yes</td>
<td data-type="Number" data-value="123">123</td>
</tr>
<tr>
<td>Butter</td>
<td data-type="Number" data-style="Currency" data-value=".89">$ .89</td>
<td data-type="Boolean" data-value="0">no</td>
<td data-type="Number" data-value="0">0</td>
</tr>
</table>
<button onclick="tablesToExcel(['tbl1','tbl2'], ['Customers','Products'], 'TestBook.xls', 'Excel')">Export to Excel</button>
<button onclick="tablesToExcel(['tbl1','tbl2'], ['Customers','Products'], 'TestBook.xls', 'Calc')">Export to Calc</button>
Fiddle: http://jsfiddle.net/qxLn3h86/
Greetings
Axel