How we can update Table of contents [TOC] in O365/Word API JavaScript/Office JS

To creating TOC we had done in pervious post click here.

Word.run(async function (context) {
  • first we need to get paragraphs from Word document and find Heading1,2,3
const currentDoc = context.document;
            const body = currentDoc.body;
            const paragraphs = currentDoc.body.paragraphs.load('items');
            const headers = [];
            const uniqueStr = new Date().getTime();
            let insertion = '';
            await context.sync();
            for (let i = 0; i < paragraphs.items.length; i++) {
                // we can use outlineLevel 1,2,3
                if (
                    paragraphs.items[i].styleBuiltIn === 'Heading1' ||
                    paragraphs.items[i].styleBuiltIn === 'Heading2' ||
                    paragraphs.items[i].styleBuiltIn === 'Heading3'
                ) {
                    headers.push(paragraphs.items[i]);
                }
            }
            if (headers.length > 0) {
                headers.map(async function (item) {
                    const bookmarkName = `_Toc${item.outlineLevel + item._Id + uniqueStr}`;
                    insertion += `<w:p>
                    <w:pPr>
                        <w:pStyle w:val='TOC${item.outlineLevel}'/>
                        <w:tabs>
                            <w:tab w:val='right' w:leader='dot' w:pos='9000'/>
                            <w:tab w:val="left" w:pos="9000" w:leader='dot'/>
                        </w:tabs>
                    </w:pPr>
                    <w:pPr>
                        <w:spacing w:after="0" w:before="0" w:beforeAutospacing="0" w:afterAutospacing="0" w:lineRule="auto" w:line="200"/>
                        ${
                            item.outlineLevel === 2
                                ? `<w:ind w:left='220'/>`
                                : item.outlineLevel === 3
                                ? `<w:ind w:left='440'/>`
                                : `<w:ind w:left='0'/>`
                        }
                    </w:pPr>
                    <w:r>
                        <w:rPr>
                            <w:noProof/>
                            <w:webHidden/>
                        </w:rPr>
                    </w:r>
                    <w:p>
                        <w:r>
                            <w:fldChar w:fldCharType="begin"/>
                        </w:r>
                        <w:r>
                            <w:instrText xml:space="preserve"> REF ${bookmarkName} \\h </w:instrText>
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType="separate"/>
                        </w:r>
                        <w:r>
                            <w:t>${item.text.toString()}</w:t>
                        </w:r>
                        <w:r>
                            <w:tab/>
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType='separate'/>
                        </w:r>
                        <w:r>
                        <w:t xml:space="preserve">1</w:t> 
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType="end"/>
                        </w:r>
                    </w:p>
                </w:p>`;
                    const insertRange: Word.Range = item.getRange();
                    insertRange.insertBookmark(bookmarkName);
                });
            } else {
                insertion = `<w:p>
                <w:r>
                    <w:fldChar w:fldCharType="begin"/>
                </w:r>
                <w:r>
                    <w:instrText xml:space="preserve"> TOC \\o "1-3" \\h \z \\u </w:instrText>
                </w:r>
                <w:r>
                    <w:fldChar w:fldCharType="separate"/>
                </w:r>
                <w:r>
                    <w:rPr>
                        <w:b/>
                        <w:bCs/>
                        <w:noProof/>
                    </w:rPr>
                    <w:t>No table of contents entries found.</w:t>
                </w:r>
                <w:r>
                    <w:rPr>
                        <w:b/>
                        <w:bCs/>
                        <w:noProof/>
                    </w:rPr>
                    <w:fldChar w:fldCharType="end"/>
                </w:r>
            </w:p>`;
            }
  • one first step we found Heading1,2,3.Now we need to find tag in content controls [TOCWinplaybox]
context.load(body, ['items', 'contentControls']);
await context.sync();
const contentControlsWithTag = context.document.contentControls.getByTag('TOCWinplaybox');
// Queue a command to load the tag property for all of content controls.
context.load(contentControlsWithTag, 'tag');
await context.sync();
  • now we will find content controls is exists or not
if (contentControlsWithTag.items.length === 0) {
                // create content controls[contentControlsWithTag] not found
                insertXML(context.document.body, insertion, Word.InsertLocation.start);
                context.document.body.paragraphs.getFirst().select();
            } else {
                return context.sync().then(function () {
                    //the contentControlsWithTag is always returned as an array of items
                    contentControlsWithTag.items[0].cannotEdit = false;
                    insertXML(contentControlsWithTag.items[0], insertion, Word.InsertLocation.replace);
                    contentControlsWithTag.items[0].getRange('Start').select();
                });
            }
await context.sync();
        }).catch((error)=>{
 console.log('Error: ' + error);
        if (error instanceof OfficeExtension.Error) {
            console.log('Debug info: ' + JSON.stringify(error.debugInfo));
            console.log('Trace info: ' + JSON.stringify(error.traceMessages));
        }
});
  • InsertXML function rather than writing multiple times
const insertXML = (property: Word.Body | Word.ContentControl, insertion: string, location: Word.InsertLocation) => {
        return property.insertOoxml(
            `<pkg:package xmlns:pkg='http://schemas.microsoft.com/office/2006/xmlPackage'>
            <pkg:part pkg:name='/_rels/.rels' pkg:contentType='application/vnd.openxmlformats-package.relationships+xml' pkg:padding='512'>
                <pkg:xmlData>
                    <Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>
                        <Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='word/document.xml'/>
                    </Relationships>
                </pkg:xmlData>
            </pkg:part>
            <pkg:part pkg:name='/word/document.xml' pkg:contentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'>
                <pkg:xmlData>
                    <w:document xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
                        <w:body>
                            <w:sdt>
                                <w:sdtPr>
                                    <w:lock w:val="contentLocked"/>
                                </w:sdtPr>
                                <w:sdtContent>
                                    <w:p>
                                        <w:pPr>
                                            <w:alias w:val="Table of Contents"/>
                                            <w:tag w:val="TOCWinplaybox"/>
                                            <w:spacing w:after="480"/>
                                            <w:jc w:val="center"/>
                                            <w:pStyle w:val='TOCHeading'/>
                                        </w:pPr>
                                        <w:r>
                                            <w:rPr>
                                                <w:keepNext/>
                                                <w:keepLines/>
                                                <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/>
                                                <w:sz w:val="32"/>
                                                <w:szCs w:val="32"/>
                                                <w:b/>
                                                <w:caps/>
                                            </w:rPr>
                                            <w:t>Table of Contents</w:t>
                                        </w:r>
                                    </w:p>  
                                    ${insertion}
                                </w:sdtContent>
                            </w:sdt>
                        </w:body>
                    </w:document>
                </pkg:xmlData>
            </pkg:part>
        </pkg:package>`,
            location
        );
    };

Now we have inserting + updating feature of TOC in Office js with the help of XML [insertOoxml]

FULL CODE:

Word.run(async function (context) {
            const currentDoc = context.document;
            const body = currentDoc.body;
            const paragraphs = currentDoc.body.paragraphs.load('items');
            const headers = [];
            const uniqueStr = new Date().getTime();
            let insertion = '';
            await context.sync();
            for (let i = 0; i < paragraphs.items.length; i++) {
                // we can use outlineLevel 1,2,3
                if (
                    paragraphs.items[i].styleBuiltIn === 'Heading1' ||
                    paragraphs.items[i].styleBuiltIn === 'Heading2' ||
                    paragraphs.items[i].styleBuiltIn === 'Heading3'
                ) {
                    headers.push(paragraphs.items[i]);
                }
            }
            if (headers.length > 0) {
                headers.map(async function (item) {
                    const bookmarkName = `_Toc${item.outlineLevel + item._Id + uniqueStr}`;
                    insertion += `<w:p>
                    <w:pPr>
                        <w:pStyle w:val='TOC${item.outlineLevel}'/>
                        <w:tabs>
                            <w:tab w:val='right' w:leader='dot' w:pos='9000'/>
                            <w:tab w:val="left" w:pos="9000" w:leader='dot'/>
                        </w:tabs>
                    </w:pPr>
                    <w:pPr>
                        <w:spacing w:after="0" w:before="0" w:beforeAutospacing="0" w:afterAutospacing="0" w:lineRule="auto" w:line="200"/>
                        ${
                            item.outlineLevel === 2
                                ? `<w:ind w:left='220'/>`
                                : item.outlineLevel === 3
                                ? `<w:ind w:left='440'/>`
                                : `<w:ind w:left='0'/>`
                        }
                    </w:pPr>
                    <w:r>
                        <w:rPr>
                            <w:noProof/>
                            <w:webHidden/>
                        </w:rPr>
                    </w:r>
                    <w:p>
                        <w:r>
                            <w:fldChar w:fldCharType="begin"/>
                        </w:r>
                        <w:r>
                            <w:instrText xml:space="preserve"> REF ${bookmarkName} \\h </w:instrText>
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType="separate"/>
                        </w:r>
                        <w:r>
                            <w:t>${item.text.toString()}</w:t>
                        </w:r>
                        <w:r>
                            <w:tab/>
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType='separate'/>
                        </w:r>
                        <w:r>
                        <w:t xml:space="preserve">1</w:t> 
                        </w:r>
                        <w:r>
                            <w:fldChar w:fldCharType="end"/>
                        </w:r>
                    </w:p>
                </w:p>`;
                    const insertRange: Word.Range = item.getRange();
                    insertRange.insertBookmark(bookmarkName);
                });
            } else {
                insertion = `<w:p>
                <w:r>
                    <w:fldChar w:fldCharType="begin"/>
                </w:r>
                <w:r>
                    <w:instrText xml:space="preserve"> TOC \\o "1-3" \\h \z \\u </w:instrText>
                </w:r>
                <w:r>
                    <w:fldChar w:fldCharType="separate"/>
                </w:r>
                <w:r>
                    <w:rPr>
                        <w:b/>
                        <w:bCs/>
                        <w:noProof/>
                    </w:rPr>
                    <w:t>No table of contents entries found.</w:t>
                </w:r>
                <w:r>
                    <w:rPr>
                        <w:b/>
                        <w:bCs/>
                        <w:noProof/>
                    </w:rPr>
                    <w:fldChar w:fldCharType="end"/>
                </w:r>
            </w:p>`;
            }

            context.load(body, ['items', 'contentControls']);

            await context.sync();
            const contentControlsWithTag = context.document.contentControls.getByTag('TOCWinplaybox');
            // Queue a command to load the tag property for all of content controls.
            context.load(contentControlsWithTag, 'tag');
            await context.sync();
            if (contentControlsWithTag.items.length === 0) {
                // create content controls[contentControlsWithTag] not found
                insertXML(context.document.body, insertion, Word.InsertLocation.start);
                context.document.body.paragraphs.getFirst().select();
            } else {
                return context.sync().then(function () {
                    //the contentControlsWithTag is always returned as an array of items
                    contentControlsWithTag.items[0].cannotEdit = false;
                    insertXML(contentControlsWithTag.items[0], insertion, Word.InsertLocation.replace);
                    contentControlsWithTag.items[0].getRange('Start').select();
                });
            }
            await context.sync();
        }).catch((error)=>{
 console.log('Error: ' + error);
        if (error instanceof OfficeExtension.Error) {
            console.log('Debug info: ' + JSON.stringify(error.debugInfo));
            console.log('Trace info: ' + JSON.stringify(error.traceMessages));
        }
});

const insertXML = (property: Word.Body | Word.ContentControl, insertion: string, location: Word.InsertLocation) => {
        return property.insertOoxml(
            `<pkg:package xmlns:pkg='http://schemas.microsoft.com/office/2006/xmlPackage'>
            <pkg:part pkg:name='/_rels/.rels' pkg:contentType='application/vnd.openxmlformats-package.relationships+xml' pkg:padding='512'>
                <pkg:xmlData>
                    <Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>
                        <Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='word/document.xml'/>
                    </Relationships>
                </pkg:xmlData>
            </pkg:part>
            <pkg:part pkg:name='/word/document.xml' pkg:contentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'>
                <pkg:xmlData>
                    <w:document xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
                        <w:body>
                            <w:sdt>
                                <w:sdtPr>
                                    <w:lock w:val="contentLocked"/>
                                </w:sdtPr>
                                <w:sdtContent>
                                    <w:p>
                                        <w:pPr>
                                            <w:alias w:val="Table of Contents"/>
                                            <w:tag w:val="TOCWinplaybox"/>
                                            <w:spacing w:after="480"/>
                                            <w:jc w:val="center"/>
                                            <w:pStyle w:val='TOCHeading'/>
                                        </w:pPr>
                                        <w:r>
                                            <w:rPr>
                                                <w:keepNext/>
                                                <w:keepLines/>
                                                <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/>
                                                <w:sz w:val="32"/>
                                                <w:szCs w:val="32"/>
                                                <w:b/>
                                                <w:caps/>
                                            </w:rPr>
                                            <w:t>Table of Contents</w:t>
                                        </w:r>
                                    </w:p>  
                                    ${insertion}
                                </w:sdtContent>
                            </w:sdt>
                        </w:body>
                    </w:document>
                </pkg:xmlData>
            </pkg:part>
        </pkg:package>`,
            location
        );
    };

Leave a Reply

Your email address will not be published.