Kasper Kamperman
Kasper Kamperman

Reputation: 111

Understanding binding and selection in Word Add-in

I'm trying to build an add-in with similar behaviour like the comment system.

  1. I select a part of text.
  2. Press a button in my add-in. A card is created that links to that text.
  3. I do something else, like write text on a different position.
  4. When I press the card in my add-in, I'd like to jump back to the selected text (in point 1).

I studied the API, documentation. And learned that I could do something like that with Bindings. A contentcontrol might also be an option, although I noticed that you can't connect and eventhandler (it's in beta). I might need an eventhandler to track changes later.

Create binding (step 2)

Office.context.document.bindings.addFromSelectionAsync(Office.BindingType.Text, { id: 'MyBinding' }, (asyncResult) => {
  if (asyncResult.status == Office.AsyncResultStatus.Failed) {
    console.log('Action failed. Error: ' + asyncResult.error.message);
  } else {
    console.log('Added new binding with id: ' + asyncResult.value.id);
  }
});

Works. Then I click somewhere else in my document, to continue with step 4.

View binding (step 4).

So I click the card and what to jump back to that text binding, with the binding selected.

I figured there are multiple ways.

Method #1

Use the Office.select function below logs the text contents of the binding. However, it doesn't select that text in the document.

Office.select("bindings#MyBinding").getDataAsync(function (asyncResult) {
  if (asyncResult.status == Office.AsyncResultStatus.Failed) {
  } 
  else {
    console.log(asyncResult.value);
  }
});

Method #2

Use the GoToById function to jump to the binding.

Office.context.document.goToByIdAsync("MyBinding", Office.GoToType.Binding, function (asyncResult) {
  let val = asyncResult.value;
  console.log(val);
});

This shows like a blue like frame around the text that was previously selected and puts the cursor at the start.

enter image description here

I'd prefer that I don't see that frame (no idea if that's possible) and I would like to the text selected.

There is the Office.GoToByIdOptions interface that mentions:

In Word: Office.SelectionMode.Selected selects all content in the binding.

I don't understand how pass that option in the function call though and I can't find an example. Can I use this interface to get the selection?

https://learn.microsoft.com/en-us/javascript/api/office/office.document?view=common-js-preview#office-office-document-gotobyidasync-member(1)

goToByIdAsync(id, goToType, options, callback)

If there are other ways to do this, I'd like to know that as well.

Upvotes: 1

Views: 607

Answers (2)

jonsson
jonsson

Reputation: 1301

Sure someone can provide a better answer than this, as it's unfamiliar territory for me, but...

When you create a Binding from the Selection in Word, you're going to get a Content Control anyway. So to avoid having something that looks like a content control with the blue box, you either have to modify the control's display or you have to find some other way to reference a region of your document. In the traditional Word Object model, you could use a bookmark, for example. But the office-js APIs do not seem very interested in them.

However, when you create a Binding, which is an Office object, you don't get immediate access to the Content Control's properties (since that's a Word object). So instead of creating the Binding then trying to modify the Content Control, you may be better off creating the Content Control then Binding to it.

Something like this:

async function markTarget() {

  Word.run(async (context) => {
    const cc = context.document.getSelection().insertContentControl();
    // "Hidden" means you don't get the "Bounding Box" 
    // (blue box with Title), or the Start/End tag view
    cc.appearance = "Hidden";
    // Provide a Title so we have a Name to bind to
    cc.title = "myCC";
    // If you don't want users changing the content, you 
    // could uncomment the following line 
    //cc.cannotDelete = true;
    return context.sync()
      .then(
         () => { 
           console.log("Content control inserted");
           // Now create a binding using the named item
           Office.context.document.bindings.addFromNamedItemAsync("myCC",
           Office.BindingType.Text, 
           { id: 'MyBinding' });
         },
         () => console.log("Content control insertion failed")
       ).then(
           () => console.log("Added new binding"),
           () => console.log("Binding creation failed")
         )
  });            
}

So why not just create the ContentControl, name it, and then you should be able to select it later using its Title, right? Well, getting the "data" from a control is one thing. Actually selecting it doesn't seem straightforward in the API, whereas Selecting a Binding seems to be.

So this code is pretty similar to your approach, but adds the parameter to select the whole text. The syntax for that is really the same syntax as { id: 'MyBinding' } in the code you already have.

function selectTarget() {
  Office.context.document.goToByIdAsync(
    "MyBinding",
    Office.GoToType.Binding,
      { selectionMode: Office.SelectionMode.Selected },
      function(asyncResult) {
        let val = asyncResult.value;
        console.log(val);
      }
  );
}

Both the Binding and the ContentControl (and its Title) are persisted when you save/reopen the document. In this case, the Binding is persisted as a piece of XML that stores the type ("text"), name ("MyBinding") and a reference to the internal ID of the content control, which is a 32-bit number, although that is not immediately obvious when you look at the XML - in an example here, the Id Word stores for the ContentControl is -122165626, but "Office" stores the ID for the Binding as 4172801670, but that's because they are using the two different two's complement representations of the same number.

Upvotes: 2

Kasper Kamperman
Kasper Kamperman

Reputation: 111

With some help I could figure it out. I learned that an Interface is just an object.

So in this case:

const options = {
  selectionMode: Office.SelectionMode.Selected
};

Office.context.document.goToByIdAsync("MyBinding", Office.GoToType.Binding, options, function (asyncResult) {
  console.log(asyncResult);
});

This gives the selected result.

Upvotes: 2

Related Questions