2

I have a document with three ContentControl objects that looks like this:

screenshot of document

Here is the .docx file in its entirety - but essentially the markup of the document body looks like this:

<w:body>
    <w:p w:rsidR="0075044D" w:rsidRDefault="0075044D">
        <w:r>
            <w:t xml:space="preserve">Video provides a powerful way to help you </w:t>
        </w:r>
        <w:sdt>
            <w:sdtPr>
                <w:alias w:val="cc1"/>
                <w:tag w:val="prove"/>
                <w:id w:val="806369342"/>
                <w:placeholder>
                    <w:docPart w:val="1F3FDE3D075A4E8AADE251C4E318E379"/>
                </w:placeholder>
                <w15:color w:val="FF9900"/>
                <w15:appearance w15:val="tags"/>
                <w:text/>
            </w:sdtPr>
            <w:sdtContent>
                <w:r>
                    <w:t>prove</w:t>
                </w:r>
            </w:sdtContent>
        </w:sdt>
        <w:r>
            <w:t xml:space="preserve"> your point. When you click Online Video, you can paste in the embed code for the video you want to add. You can also </w:t>
        </w:r>
        <w:sdt>
            <w:sdtPr>
                <w:alias w:val="cc2"/>
                <w:tag w:val="number 2"/>
                <w:id w:val="1463999480"/>
                <w:placeholder>
                    <w:docPart w:val="1F3FDE3D075A4E8AADE251C4E318E379"/>
                </w:placeholder>
                <w15:color w:val="FF0000"/>
                <w15:appearance w15:val="tags"/>
            </w:sdtPr>
            <w:sdtContent>
                <w:r>
                    <w:t>type</w:t>
                </w:r>
            </w:sdtContent>
        </w:sdt>
        <w:r>
            <w:t xml:space="preserve"> a keyword to search online for the video that best fits your document.</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="0075044D" w:rsidRDefault="0075044D">
        <w:r>
            <w:t xml:space="preserve">To make your document look professionally produced, Word provides </w:t>
        </w:r>
        <w:sdt>
            <w:sdtPr>
                <w:alias w:val="cc3"/>
                <w:tag w:val="xxx"/>
                <w:id w:val="1703202634"/>
                <w:placeholder>
                    <w:docPart w:val="1F3FDE3D075A4E8AADE251C4E318E379"/>
                </w:placeholder>
                <w15:color w:val="FF99CC"/>
                <w15:appearance w15:val="tags"/>
                <w:text/>
            </w:sdtPr>
            <w:sdtContent>
                <w:r>
                    <w:t>header</w:t>
                </w:r>
            </w:sdtContent>
        </w:sdt>
        <w:r>
            <w:t>, footer, cover page, and text box designs that complement each other. For example, you can add a matching cover page, header, and sidebar. Click Insert and then choose the elements you want from the different galleries.</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="0075044D" w:rsidRDefault="0075044D"/>
    <w:p w:rsidR="00000000" w:rsidRDefault="0075044D"/>
    <w:sectPr w:rsidR="00000000">
        <w:pgSz w:w="11906" w:h="16838"/>
        <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
        <w:cols w:space="708"/>
        <w:docGrid w:linePitch="360"/>
    </w:sectPr>
</w:body> 

When I run the code below, only the control in the middle is returned by document.contentControls and therefore changed by my code. Any ideas why the other two controls are not returned? Did anyone else encounter this issue? Is there a way to fix it?

Word.run(function (context) {
    var myContentControls = context.document.contentControls;
    myContentControls.load("tag");

    return context.sync()
        .then(function () {
            for (var i = 0; i < myContentControls.items.length; i++)
            {
                myContentControls.items[i].color = "blue";
                myContentControls.items[i].title = "myCC";
                myContentControls.items[i].appearance = "tags";
            }
            return context.sync();
        });
}).catch(OfficeHelpers.Utilities.log);

Here is a ScriptLab gist for convenience.

Interestingly, this VBA code returns the correct result (3):

Sub Main()
 MsgBox ActiveDocument.ContentControls.Count
End Sub

I have only tried this on Windows 10 / Office 365 desktop client.

Cindy Meister
  • 25,071
  • 21
  • 34
  • 43
Leo
  • 5,013
  • 1
  • 28
  • 65
  • 1
    Just to be certain: The content controls are all in the body text of the document? Nothing in a text box, for example? – Cindy Meister Feb 06 '20 at 14:26
  • hello @CindyMeister - yes they are all in the body. The document is attached if you would like to inspect... – Leo Feb 06 '20 at 14:44
  • 1
    As a matter of principle I don't work with downloaded files... If I did have the file, the next thing I'd try would be the VBA equivalent - see if it has the same problem or whether it "sees" all the content controls. That way one would know if the problem is in the JS APIs or in the document. – Cindy Meister Feb 06 '20 at 15:04
  • @CindyMeister - I added the markup for the document body... I will try with VBA. – Leo Feb 06 '20 at 15:15
  • @CindyMeister - I am surprised to say that in VBA, all the content controls are found... – Leo Feb 06 '20 at 15:34
  • 1
    The reason is (probably) the content controls that are missed are "plain text" types of controls, rather than "rich text". The Word JS APIs only recognizes "rich text" content controls. In my test, the "plain text" controls were skipped; the "rich text" controls were changed. (It's in the JS API documentation, I believe.) – Cindy Meister Feb 06 '20 at 18:39
  • @CindyMeister - how come the second one is picked up then? It looks the same as the first and the third... Could it be that empty ``? – Leo Feb 06 '20 at 20:45
  • 1
    You won't see a difference between the two types on the document surface. But you'll find you cannot apply formatting to characters in a plain text content control... – Cindy Meister Feb 06 '20 at 21:08
  • Ok, you are right... That is the reason. :( – Leo Feb 06 '20 at 21:16
  • 1
    @CindyMeister Can you make your comment an answer, so the question will count as answered in Stack's stats? Thanks. – Rick Kirkham Feb 06 '20 at 21:20
  • @RickKirkham - I don't think this is a satisfactory answer though... Should I log it as a bug in github? Is it already logged? – Leo Feb 06 '20 at 21:21
  • 1
    Depends on what you mean by satisfactory. It is documented ([Word.ContentControl](https://learn.microsoft.com/javascript/api/word/word.contentcontrol)) that only rich content controls are supported, so Cindy's comment is the correct answer to why only the middle CC is being returned. – Rick Kirkham Feb 06 '20 at 21:29
  • @RickKirkham - Yes, it is now with the reference link you provided :) – Leo Feb 06 '20 at 21:34
  • 1
    @RickKirkham Done :-) But are you sure this hasn't come up before? If it has, it should actually be closed as a duplicate (or another should be closed as a duplicate of this one - depending on which has the best quality of Q&A). – Cindy Meister Feb 06 '20 at 22:19

2 Answers2

3

As stated in the Word JS APIs' documentation for content controls, only Rich Text content controls are supported / recognized. So code will not "see" plain text content controls.

Whether a content control is plain text or rich text is not visually recognizable (unless it contains formatting or content other than text). There's also nothing in the Word Open XML to differentiate the type of content control, unless it contains formatting or a non-text object.

Cindy Meister
  • 25,071
  • 21
  • 34
  • 43
1

As Cindy Meister already said, the Word OfficeJS API only returns RichText content controls - see Word.ContentControl.

If you dive into the OOXML, you'll find that plain text context controls (the ones that are not returned) main difference from the rich text ones is that they are marked with <w:text/>. This essentially means that if that tag is removed, then the plain text content control becomes a rich text content control. So here is my workaround to this issue:

var ooxml = context.document.body.getOoxml();
await context.sync();
var newxml = ooxml.value.replace(/<w:text\/>/g, '');

context.document.body.insertOoxml(newxml, Word.InsertLocation.replace);

var myContentControls = context.document.contentControls;
myContentControls.load();
await context.sync();

console.log(myContentControls.items.length); // now returns 3. Yay!!!

return context.sync();

Nota Bene: getOoxml() and insertOoxml() are slow methods and this workaround will take a long time to execute, so only use when strictly necessary.

Leo
  • 5,013
  • 1
  • 28
  • 65