1

I have created an Excel Task Pane Web Add-in in Visual Studio. Since then, I have introduced TypeScript, upgraded the project files from .NET Framework to .NET 7, and configured to use shared runtime. Currently I do not use the Task Pane, only executing actions.

At this point, I have made an Excel Web Add-in, which have a custom ribbon tab, but this add-in only works with a specifically formatted workbook; that is the add-in expect certain worksheets, tables, or named range to be present in the workbook to work. To make sure that the ribbon does not show when the opened workbook isn't supported, I would like to replace this ribbon tab with a contextual ribbon tab, and show the tab only, if the add-in recognizes the workbook, by detecting certain elements of it, or finds a meta-information embedded in the workbook.

I have followed Microsoft's Create custom contextual tabs in Office add-ins, and managed to redefine the custom ribbon tab as a contextual tab, however, the custom ribbon tab still shows until it is first updated.

When I also remove the custom ribbon tab definition from the manifest file, then the requestUpdate method call throws an error saying something along the line, that ‘null’ doesn't have a property of ‘tabs’. Seemingly the custom ribbon tad doesn't support the OverriddenByRibbonApi tag, and if I specify it for the group, the tab will show, but without any button. I could workaround this, by defining the groups in the Home tab, and mark them with the <OverriddenByRibbonApi>true</OverriddenByRibbonApi> markup. This has some drawback and would only prefer this approach as a last-effort solution. I've also noticed, that even though the ribbon shows for every workbook that is opened, the add-in only starts executing when the user interacts with the add-in, and after that, every time that workbook is loaded, the add-in will start executing.

I checked the content of the workbook and noticed that the one I was testing with holds reference to my add-in, but the workbook I created even before the add-in existed, does not. This behaviour would be perfect for my purposes, unfortunately I do not even know how to look for a documentation describing how to bind an add-in to a workbook to load the add-in.

How can I make a contextual ribbon tab, without the need to define any custom ribbon tab in the manifest file, and only show the contextual ribbon tab, if and only if the workbook specifically designed for that add-in?

Excerpt from my manifest file:

<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp 
          xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 
          xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides"
          xsi:type="TaskPaneApp">
  <!-- ... -->
  <Hosts>
    <Host Name="Workbook" />
  </Hosts>
  <Requirements>
    <Sets DefaultMinVersion="1.1">
      <Set Name="SharedRuntime" MinVersion="1.2"/>
    </Sets>
  </Requirements>
  <DefaultSettings>
    <SourceLocation DefaultValue="~remoteAppUrl/Functions/FunctionFile.html" />
  </DefaultSettings>
  <!-- ... -->
  <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
    <Hosts>
      <Host xsi:type="Workbook">
        <Runtimes>
          <Runtime resid="AddIn.Functions.Url" lifetime="long"/>
        </Runtimes>
        <DesktopFormFactor>
          <FunctionFile resid="..." />
          <ExtensionPoint xsi:type="PrimaryCommandSurface">
            <!-- Would like to remove this. -->
            <CustomTab id="...">
              <Group id="...">
                <OverriddenByRibbonApi>true</OverriddenByRibbonApi>
                <!-- ... -->
              </Group>
              <Label resid="..."/>
            </CustomTab>
          </ExtensionPoint>
        </DesktopFormFactor>
      </Host>
    </Hosts>
    <Resources>
      <!-- ... -->
    </Resources>
  </VersionOverrides>
</OfficeApp>

Excerpt from the functionFile.ts:

type OfficeRibbon = any;

function getGlobal(): any {
  return typeof self   !== undefined ? self
       : typeof window !== undefined ? window
       : typeof global !== undefined ? global
       : undefined;
}

function getRibbon(): OfficeRibbon {
  return getGlobal().ribbon;
}

function setRibbon(value: OfficeRibbon): void {
  if (trace) console. Debug("Update ribbon:", value);
  getGlobal().ribbon = value;
}

Office.onReady(async (info: { host: Office.HostType, platform: Office.PlatformType }): Promise<any> => {
  if (trace) console.debug("Office ready.");
  if (trace) console.debug(info);

  if (Office.context.requirements.isSetSupported("RibbonApi", "1.2")) {
    let fetchJsonResponse: Response;
    let jsonText: string;
    let ribbonJson: any;
    let host: string;

    console.log("Ribbon API 1.2 is supported");

    host = window.location.href.match(/https:\/\/[^/]*\//i)[0];
    supportRibbon12 = true;
    if (trace) console.debug("Fetching ribbon descriptor file.");
    fetchJsonResponse = await fetch('../Scripts/ribbon.json');

    if (trace) console.debug("Fetching ribbon descriptor file content.");
    // In the JSON file, I referred to host as ~remoteAppUrl.
    // This may change for localization purposes.
    jsonText = (await fetchJsonResponse.text()).replace(/~remoteAppUrl/g, host.slice(0, -1));

    ribbonJson = JSON.parse(jsonText);
    if (trace) console.debug("Creating ribbon.", ribbonJson);
    setRibbon(ribbonJson);
    await Office.ribbon.requestCreateControls(ribbonJson);
  }

  await Excel.run(async (context: Excel.RequestContext): Promise<void> => {
    await onWorkbookLoaded(context);
    if (trace) {
      // Additional debugging code.
      // ...
    }
  });

  return info;
});

async function onWorkbookLoaded(context: Excel.RequestContext) {
  // Check if workbook have signature, and if so, continue with following:
  await prepareWorkbook(context);
}

async function prepareWorkbook(context: Excel.RequestContext) {
  // ...

  if (supportRibbon12) {
    try {
      let ribbon: OfficeRibbon = getRibbon();
      if (trace) console.debug("Update ribbon state.");
      ribbon.tabs[0].visible = true;
      await Office.ribbon.requestUpdate(ribbon);
    } catch (error) {
      console.error(error);
    }
  }

  console.info("Add-in prepared.");
}

Edit: The exception message I get if I remove the <CustomTab> from the manifest:

RichApi.Error: Cannot read properties of null (reading 'tabs')
    at new n (excel-web-16.00.js:25:314669)
    at i.processRequestExecutorResponseMessage (excel-web-16.00.js:25:378835)
    at excel-web-16.00.js:25:376898
Adam L. S.
  • 825
  • 1
  • 7
  • 14
  • I found documentation on how to bind the document with an add-in: https://learn.microsoft.com/en-us/office/dev/add-ins/develop/automatically-open-a-task-pane-with-a-document#use-open-xml-to-tag-the-document – Adam L. S. May 30 '23 at 10:04

1 Answers1

0

How can I make a contextual ribbon tab, without the need to define any custom ribbon tab in the manifest file, and only show the contextual ribbon tab, if and only if the workbook specifically designed for that add-in?

It is not possible to place a contextual tab without creating a custom tab first. That is by design. You can post or vote for an existing feature request on Tech Community where they are considered when the Office dev team goes through the planning process.


The tab object can also have an optional visible property that specifies whether the tab is visible immediately when the add-in starts up. Since contextual tabs are normally hidden until a user event triggers their visibility (such as the user selecting an entity of some type in the document), the visible property defaults to false when not present. See Create custom contextual tabs in Office Add-ins for more information.

Some combinations of platform, Office application, and Office build don't support requestCreateControls. Your add-in should be designed to provide an alternate experience to users who are running the add-in on one of those combinations. The Implement an alternate UI experience when custom contextual tabs are not supported describes two ways of providing a fallback experience.

You may find the sample add-in helpful.

Eugene Astafiev
  • 47,483
  • 3
  • 24
  • 45
  • I already set the `visible` property of the `tab` object, and one of the alternate UI experience define a custom tab and mark the group or control *but not the tab* with `OverriddenByRibbonApi` tag, and I do not wish to simulate ribbon buttons with task pane. I followed the sample by using a `ribbon` global variable, which worked, but the tab appeared from the manifest without the group until changed visibility. The sample uses both a custom tab *and* a contextual tab, and if I don't have a custom tab, I can't change the visibility of the contextual tab. – Adam L. S. May 29 '23 at 09:27
  • 1
    Try to post this as an issue to https://github.com/OfficeDev/office-js/issues . In COM add-ins you could create contextual tabs without a requirement to create any other ribbon tabs. – Eugene Astafiev May 29 '23 at 11:02