Can't Seem To Use Ranges across calls to Word.run(context => {...}) in Office JS
Asked Answered
O

1

6

My project involves parsing a word document to extract a set of tokens then allowing the user to selectively perform bulk actions on those tokens. Because of the UI step, I need to be able to call myRange.select() and myRange.font.set(...) on a subsequent Word.run(ctx => {...}) call from run which extracted the tokens.

If this is more appropriate as a stackoverflow post, I apologize and will repost there but it appears that the library is not matching the API, as I understand it. I could definitely be mistaken though.

Expected Behavior

I expect that calling Word.run(ctx => myRange.select()) would cause that range to be selected as that range was added to context.trackedObjects on a previous run.

Current Behavior

Nothing happens, not even an error to the console.

note that the commented code of keeping the context object on the Chunk class and using that for subsequent runs will work on Word Online / Chrome but does not work in Windows or OSX Word

Your Environment

  • Platform [PC desktop, Mac, iOS, Office Online]: Win11 desktop, OSX, Word Online (all most recent)
  • Host [Excel, Word, PowerPoint, etc.]: Word
  • Office version number: most recent
  • Operating System: OSX / Win11
  • Browser (if using Office Online): Chrome

Code:

import * as React from 'react'
import { Container, ListGroup, ListGroupItem, Button, Label, Input, ButtonGroup, Row } from 'reactstrap'

class Chunk {
    range: Word.Range
    text: string
    // context: Word.RequestContext
    constructor(t: string, r: Word.Range, ctx: Word.RequestContext) {
        this.range = r
        this.text = t
        ctx.trackedObjects.add(r)
        r.track()
        // this.context = ctx
    }
    async select(ctx: Word.RequestContext) {
        console.log('select')
        this.range.select('Select')
        ctx.sync()
    }
}

const getChunks = async () => {
    return Word.run(async context => {
        let paragraphs = context.document.body.paragraphs.load()
        let wordRanges: Array<Word.RangeCollection> = []
        await context.sync()
        paragraphs.items.forEach(paragraph => {
            const ranges = paragraph.getTextRanges([' ', ',', '.', ']', ')'], true)
            ranges.load('text')
            wordRanges.push(ranges)
        })
        await context.sync()
        let chunks: Chunk[] = []
        wordRanges.forEach(ranges => ranges.items.forEach(range => {
            chunks.push(new Chunk(range.text, range, context))
        }))
        await context.sync()
        return chunks
    })

}

interface ChunkControlProps { chunk: Chunk; onSelect: (e: React.MouseEvent<HTMLElement>) => void }
export const ChunkControl: React.SFC<ChunkControlProps> = ({ chunk, onSelect}) => {
    return (
        <div style={{marginLeft: '0.5em'}}><a href='#' onClick={onSelect}>{chunk.text}</a></div>
    )
}
export class App extends React.Component<{title: string}, {chunks: Chunk[]}> {
    constructor(props, context) {
        super(props, context)
        this.state = { chunks: [] }
    }

    componentDidMount() { this.click() }

    click = async () => {
        const chunks = await getChunks()
        this.setState(prev => ({ ...prev, chunks: chunks }))
    }

    onSelectRange(chunk: Chunk) {
        return async (e: React.MouseEvent<HTMLElement>) => {
            e.preventDefault()
            Word.run(ctx => chunk.select(ctx))
        }
    }

    render() {
        return (
            <Container fluid={true}>
                <Button color='primary' size='sm' block className='ms-welcome__action' onClick={this.click}>Find Chunks</Button>
                <hr/>
                <ListGroup>
                    {this.state.chunks.map((chunk, idx) => (
                        <ListGroupItem key={idx}>
                            <ChunkControl  onSelect={this.onSelectRange(chunk)} chunk={chunk}/>
                        </ListGroupItem>
                    ))}
                </ListGroup>
            </Container>
        )
    };
};

Version that works on WordOnline but not Windows or OSX:

(duplicate code from above elided)

 class Chunk {
    range: Word.Range
    text: string
    context: Word.RequestContext
    constructor(t: string, r: Word.Range, ctx: Word.RequestContext) {
        this.range = r
        this.text = t
        this.context = ctx
    }
    async select() {
        this.range.select('Select')
        ctx.sync()
    }
}

const getChunks = async () => {
    return Word.run(async context => {
        ...
    })

}

...

export class App extends React.Component<{title: string}, {chunks: Chunk[]}> {
    constructor(props, context) {
        super(props, context)
        this.state = { chunks: [] }
    }

    componentDidMount() { this.click() }

    click = async () => {
        const chunks = await getChunks()
        this.setState(prev => ({ ...prev, chunks: chunks }))
    }

    onSelectRange(chunk: Chunk) {
        return async (e: React.MouseEvent<HTMLElement>) => {
            e.preventDefault()
            chunk.select()
        }
    }

    render() {
        return (
            <Container fluid={true}>
                <Button color='primary' size='sm' block className='ms-welcome__action' onClick={this.click}>Find Chunks</Button>
                <hr/>
                <ListGroup>
                    {this.state.chunks.map((chunk, idx) => (
                        <ListGroupItem key={idx}>
                            <ChunkControl  onSelect={this.onSelectRange(chunk)} chunk={chunk}/>
                        </ListGroupItem>
                    ))}
                </ListGroup>
            </Container>
        )
    };
};
Oosperm answered 21/2, 2018 at 17:54 Comment(3)
I'm also currious about the different behaviors between Online and Desktop. Could you check and see if this is Chrome specific or it any browser (Safari, Edge, Firefox, etc.) has the same issue?Kieserite
I've confirmed that in Word Online, I see the same behavior on OSX (chrome, FF, Safari) and windows (Edge and IE11) where the code, with Michael's suggestion below, works properly. In native windows, I get the GeneralException described below.\Oosperm
@MarcLaFleur-MSFT created a demo repo with screen casts of expected (chrome) and what appears to be incorrect, though i'm not certain of course, behavior on windows. github.com/maxcan/office-js-context-bug-reproOosperm
H
10

In terms of the general pattern: The trick is to do a Word.run just like you normally would, but instead of having it create a new anonymous request context, have the run resume using the context of some existing object. To resume using an existing context, you simply use one of the function overloads available off of Word.run; namely, an overload that takes in an object (or array of objects) as the first argument, and the batch as the second:

Code sample

Note that in order to be able to use the Range object across different run-s, you will have needed to call range.track() to prolong its lifetime before the finish of the first run; and you should clean it up at some point using range.untrack().

The text above, and the code sample, comes from my book Building Office Add-ins using Office.js. There is a bunch more info there as well. Pasting in one section in particular:

What happens when you forget to pass in a Range object

Related sections of the book that you might find useful:

TOC

As for your observation about a difference in behavior between Online and Windows/Mac -- that's quite interesting, and sounds like a bug that should be investigated. Would you mind filing a bug for just that particular aspect, with a minimal repro, at https://github.com/OfficeDev/office-js/issues? (Sorry for sending you back and forth, I know you'd already been there at https://github.com/OfficeDev/office-js/issues/68; but I want to separate out the conceptual issue which was really just a question (and documentation issue), versus a possible bug as seen in the difference of behavior).

Best!

~ Michael

Hightoned answered 21/2, 2018 at 18:25 Comment(8)
Thank you for the quick reply. I went out and bought the book (in part as a thank you for getting back to me so quickly, in part because your answer showed that its almost certainly a good investment). However, the approach outlined of using Word.run(range, ctx => {..}) still doesn't work on windows. I get a GeneralException in Document._GetObjectByReferenceId in windows. I've tried to step through it in the F12 tools, but haven't been successful as of yet. Unfortunately, the error doesn't come up in Word Online so can't debug in chromeOosperm
Also, I tried your patch from #37275982 in the off change that the CDN hadn't yet been updated and it didn't seem to workOosperm
@michael-zlatkovsky-microsoft repo added here github.com/maxcan/office-js-context-bug-reproOosperm
What about the code in the book, does that work for you?Hightoned
Fwiw, based on the error, it sounds like you're not calling "range.track()" before the range goes out of scope. More details on tracking in the book.Hightoned
Success! I'm checking this answer because even thought it didn't directly solve the problem entirely, it put me on the right path. The missing piece was that I needed to call track() on the ParagraphCollection, individual Paragraphs, and RangeCollections too. that did the trick on windows.Oosperm
Glad it helped :-)Hightoned
Wow. Looked at @mxc 's last comment, and on a whim I added track() to the searchResults object, when I just wanted to track a subset of the results that were returned (stored in a separate array for later). Went from getting a general exception error to working code. Thanks you two.Horsehide

© 2022 - 2024 — McMap. All rights reserved.