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.
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.
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
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>
)
};
};
(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>
)
};
};
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:
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:
Related sections of the book that you might find useful:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With