Edit

Share via


Use annotations in your Word add-in

Includes community contributions from: Abdulhadi Jarad

You can use annotations to provide feedback about grammar or other aspects of content in a Word document. The user may see colorful underlining that indicates there's an issue or other information. If the user hovers over the affected content, a popup dialog is displayed that shows them what the issue is and possible actions they can take.

APIs for working with annotations were introduced in the WordApi 1.7 requirement set and expanded in the WordApi 1.8 requirement set as part of supporting writing assistance scenarios like checking spelling and grammar or providing suggestions to improve writing.

In this article, we show how your add-in can insert feedback and critiques using annotations in a document and allow the user to react to them.

Important

These annotations aren't persisted in the document. This means that when the document is reopened, the annotations need to be regenerated. However, if the user accepts suggested changes, the changes will persist as long as the user saves them before closing the document.

Prerequisites

The annotation APIs rely on a service that requires a Microsoft 365 subscription. As such, using this feature in Word with a one-time purchase license won't work. The user must be running Word connected to a Microsoft 365 subscription so that your add-in can successfully run the annotation APIs.

Key annotation APIs

The following are the key annotation APIs.

Use annotation APIs

The following sections show how to work with annotation APIs. These examples are based on the Manage annotations code sample.

Your add-in code should use the feedback or critique results from your service run against the user's content in the document to manage annotations more dynamically.

Register annotation events

The following code shows how to register event handlers. To learn more about working with events in Word, see Work with events using the Word JavaScript API. For examples of annotation event handlers, see the following sections.

let eventContexts = [];

async function registerEventHandlers() {
  // Registers event handlers.
  await Word.run(async (context) => {
    eventContexts[0] = context.document.onParagraphAdded.add(paragraphChanged);
    eventContexts[1] = context.document.onParagraphChanged.add(paragraphChanged);

    eventContexts[2] = context.document.onAnnotationClicked.add(onClickedHandler);
    eventContexts[3] = context.document.onAnnotationHovered.add(onHoveredHandler);
    eventContexts[4] = context.document.onAnnotationInserted.add(onInsertedHandler);
    eventContexts[5] = context.document.onAnnotationRemoved.add(onRemovedHandler);
    eventContexts[6] = context.document.onAnnotationPopupAction.add(onPopupActionHandler);

    await context.sync();

    console.log("Event handlers registered.");
  });
}

onClickedHandler event handler

The following code runs when the registered onAnnotationClicked event occurs.

async function onClickedHandler(args: Word.AnnotationClickedEventArgs) {
  // Runs when the registered Document.onAnnotationClicked event occurs.
  await Word.run(async (context) => {
    const annotation: Word.Annotation = context.document.getAnnotationById(args.id);
    annotation.load("critiqueAnnotation");

    await context.sync();

    console.log(`AnnotationClicked: ID ${args.id}:`, annotation.critiqueAnnotation.critique);
  });
}

onHoveredHandler event handler

The following code runs when the registered onAnnotationHovered event occurs.

async function onHoveredHandler(args: Word.AnnotationHoveredEventArgs) {
  // Runs when the registered Document.onAnnotationHovered event occurs.
  await Word.run(async (context) => {
    const annotation: Word.Annotation = context.document.getAnnotationById(args.id);
    annotation.load("critiqueAnnotation");

    await context.sync();

    console.log(`AnnotationHovered: ID ${args.id}:`, annotation.critiqueAnnotation.critique);
  });
}

onInsertedHandler event handler

The following code runs when the registered onAnnotationInserted event occurs.

async function onInsertedHandler(args: Word.AnnotationInsertedEventArgs) {
  // Runs when the registered Document.onAnnotationInserted event occurs.
  await Word.run(async (context) => {
    const annotations = [];
    for (let i = 0; i < args.ids.length; i++) {
      let annotation: Word.Annotation = context.document.getAnnotationById(args.ids[i]);
      annotation.load("id,critiqueAnnotation");
      annotations.push(annotation);
    }

    await context.sync();

    for (let annotation of annotations) {
      console.log(`AnnotationInserted: ID ${annotation.id}:`, annotation.critiqueAnnotation.critique);
    }
  });
}

onRemovedHandler event handler

The following code runs when the registered onAnnotationRemoved event occurs.

async function onRemovedHandler(args: Word.AnnotationRemovedEventArgs) {
  // Runs when the registered Document.onAnnotationRemoved event occurs.
  await Word.run(async (context) => {
    for (let id of args.ids) {
      console.log(`AnnotationRemoved: ID ${id}`);
    }
  });
}

onPopupActionHandler event handler

The following code runs when the registered onAnnotationPopupAction event occurs.

async function onPopupActionHandler(args: Word.AnnotationPopupActionEventArgs) {
  // Runs when the registered Document.onAnnotationPopupAction event occurs.
  await Word.run(async (context) => {
    let message = `AnnotationPopupAction: ID ${args.id} = `;
    if (args.action === "Accept") {
      message += `Accepted: ${args.critiqueSuggestion}`;
    } else {
      message += "Rejected";
    }

    console.log(message);
  });
}

Insert annotations

The following code shows how to insert annotations into the selected paragraph.

async function insertAnnotations() {
  // Adds annotations to the selected paragraph.
  await Word.run(async (context) => {
    const paragraph: Word.Paragraph = context.document.getSelection().paragraphs.getFirst();
    const options: Word.CritiquePopupOptions = {
      brandingTextResourceId: "PG.TabLabel",
      subtitleResourceId: "PG.HelpCommand.TipTitle",
      titleResourceId: "PG.HelpCommand.Label",
      suggestions: ["suggestion 1", "suggestion 2", "suggestion 3"]
    };
    const critique1: Word.Critique = {
      colorScheme: Word.CritiqueColorScheme.red,
      start: 1,
      length: 3,
      popupOptions: options
    };
    const critique2: Word.Critique = {
      colorScheme: Word.CritiqueColorScheme.green,
      start: 6,
      length: 1,
      popupOptions: options
    };
    const critique3: Word.Critique = {
      colorScheme: Word.CritiqueColorScheme.blue,
      start: 10,
      length: 3,
      popupOptions: options
    };
    const critique4: Word.Critique = {
      colorScheme: Word.CritiqueColorScheme.lavender,
      start: 14,
      length: 3,
      popupOptions: options
    };
    const critique5: Word.Critique = {
      colorScheme: Word.CritiqueColorScheme.berry,
      start: 18,
      length: 10,
      popupOptions: options
    };
    const annotationSet: Word.AnnotationSet = {
      critiques: [critique1, critique2, critique3, critique4, critique5]
    };

    const annotationIds = paragraph.insertAnnotations(annotationSet);

    await context.sync();

    console.log("Annotations inserted:", annotationIds.value);
  });
}

Get annotations

The following code shows how to get annotations from the selected paragraph.

async function getAnnotations() {
  // Gets annotations found in the selected paragraph.
  await Word.run(async (context) => {
    const paragraph: Word.Paragraph = context.document.getSelection().paragraphs.getFirst();
    const annotations: Word.AnnotationCollection = paragraph.getAnnotations();
    annotations.load("id,state,critiqueAnnotation");

    await context.sync();

    console.log("Annotations found:");

    for (let i = 0; i < annotations.items.length; i++) {
      const annotation: Word.Annotation = annotations.items[i];

      console.log(`ID ${annotation.id} - state '${annotation.state}':`, annotation.critiqueAnnotation.critique);
    }
  });
}

Accept an annotation

The following code shows how to accept the first annotation found in the selected paragraph.

async function acceptFirst() {
  // Accepts the first annotation found in the selected paragraph.
  await Word.run(async (context) => {
    const paragraph: Word.Paragraph = context.document.getSelection().paragraphs.getFirst();
    const annotations: Word.AnnotationCollection = paragraph.getAnnotations();
    annotations.load("id,state,critiqueAnnotation");

    await context.sync();

    for (let i = 0; i < annotations.items.length; i++) {
      const annotation: Word.Annotation = annotations.items[i];

      if (annotation.state === Word.AnnotationState.created) {
        console.log(`Accepting ID ${annotation.id}...`);
        annotation.critiqueAnnotation.accept();

        await context.sync();
        break;
      }
    }
  });
}

Reject an annotation

The following code shows how to reject the last annotation found in the selected paragraph.

async function rejectLast() {
  // Rejects the last annotation found in the selected paragraph.
  await Word.run(async (context) => {
    const paragraph: Word.Paragraph = context.document.getSelection().paragraphs.getFirst();
    const annotations: Word.AnnotationCollection = paragraph.getAnnotations();
    annotations.load("id,state,critiqueAnnotation");

    await context.sync();

    for (let i = annotations.items.length - 1; i >= 0; i--) {
      const annotation: Word.Annotation = annotations.items[i];

      if (annotation.state === Word.AnnotationState.created) {
        console.log(`Rejecting ID ${annotation.id}...`);
        annotation.critiqueAnnotation.reject();

        await context.sync();
        break;
      }
    }
  });
}

Delete annotations

The following code shows how to delete all the annotations found in the selected paragraph.

async function deleteAnnotations() {
  // Deletes all annotations found in the selected paragraph.
  await Word.run(async (context) => {
    const paragraph: Word.Paragraph = context.document.getSelection().paragraphs.getFirst();
    const annotations: Word.AnnotationCollection = paragraph.getAnnotations();
    annotations.load("id");

    await context.sync();

    const ids = [];
    for (let i = 0; i < annotations.items.length; i++) {
      const annotation: Word.Annotation = annotations.items[i];

      ids.push(annotation.id);
      annotation.delete();
    }

    await context.sync();

    console.log("Annotations deleted:", ids);
  });
}

Deregister annotation events

The following code shows how to deregister the event handlers using their event contexts you tracked in the eventContext variable.

async function deregisterEventHandlers() {
  // Deregisters event handlers.
  await Word.run(async (context) => {
    for (let i = 0; i < eventContexts.length; i++) {
      await Word.run(eventContexts[i].context, async (context) => {
        eventContexts[i].remove();
      });
    }

    await context.sync();

    eventContexts = [];
    console.log("Removed event handlers.");
  });
}

See also