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>
        )
    };
};
max
  • 811
  • 6
  • 13
  • 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? – Marc LaFleur Feb 21 '18 at 18:41
  • 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.\ – max Feb 21 '18 at 21:39
  • @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. https://github.com/maxcan/office-js-context-bug-repro – max Feb 22 '18 at 21:23

1 Answers1

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

  • 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 chrome – max Feb 21 '18 at 21:27
  • Also, I tried your patch from https://stackoverflow.com/questions/37275482/how-can-a-range-be-used-across-different-word-run-contexts in the off change that the CDN hadn't yet been updated and it didn't seem to work – max Feb 22 '18 at 00:29
  • @michael-zlatkovsky-microsoft repo added here https://github.com/maxcan/office-js-context-bug-repro – max Feb 22 '18 at 21:24
  • What about the code in the book, does *that* work for you? – Michael Zlatkovsky - Microsoft Feb 23 '18 at 01:42
  • 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. – Michael Zlatkovsky - Microsoft Feb 23 '18 at 01:43
  • 1
    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. – max Feb 23 '18 at 18:50
  • 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. – MDMoore313 Jun 04 '20 at 01:25