Simulators
Ranges and collections
A range is a span of the document your add-in holds across context.sync() calls: a search hit, a getRange result, the range an insertText returns. Word decides where a range sits by how your add-in reached it, and the simulator follows. A search hit made in a sync that only read keeps its offsets when a later sync edits its paragraph, so replacing every hit of one search writes the later hits at stale offsets:
await Word.run(async context => {
context.document.body.insertParagraph('a a a', 'End');
await context.sync();
});
await Word.run(async context => {
const hits = context.document.body.search('a');
hits.load('items');
await context.sync();
hits.items.forEach(hit => hit.insertText('bb', 'Replace'));
await context.sync();
const paragraphs = context.document.body.paragraphs;
paragraphs.load('items/text');
await context.sync();
// paragraphs.items.map(p => p.text) -> ["", "bbbbbb a"]
});
Walk paragraphs with getNext and getPrevious. Past either end they fail as Error format says. A range’s paragraphs lists the paragraphs it touches. How an insertOoxml moves ranges is on Ranges and insertOoxml.
A collection lists what it held when the run first reached it
A collection lists the items Word resolved when your Word.run first reached it, each read as it is now, however late it first loads them. office.js sends a collection to Word in the first context.sync() after your code makes it, whether body.paragraphs, a search or getComments().
After an untracked delete, body.paragraphs reached before it, loaded or not, still lists the deleted paragraph, with empty text, and counts it. A paragraph inserted since is not among them. Reading the deleted paragraph itself, or calling anything on it, throws ItemNotFound. A range’s paragraphs, a search’s hits, comments and a thread’s replies do the same.
A comment or reply deleted since stays listed with its id, author, date and resolved state. Reading it then goes this way:
| Read | Result |
|---|---|
its content | throws GeneralException |
| the collection’s items, loaded with every property | throws GeneralException |
the deleted comment itself, or the collection’s getFirst that returns it | throws ItemNotFound |
A tracked change collection alone lists the changes as they are at each load.
To see an edit, take a new collection after it, such as body.getRange().paragraphs or a new search. Or read in the next Word.run: body.paragraphs is one object for the whole run.
A search collection loaded again lists each hit where it is now.
Differs from Word for the web: Word lists a hit an edit moved, and that your add-in hasn’t read or touched since, at its old offsets, until you read the hit itself.
A range moves with the text unless a sync that only read made it
A range a search or getRange made in a context.sync() that only read keeps its offsets in its paragraph when a later sync edits the paragraph. It then reads whatever text sits there. So replacing every hit of one search in a later sync writes the later hits at stale offsets.
These ranges move with the text instead:
- one made in a sync that wrote anything, such as an edit anywhere, or a
changeTrackingModeset to any mode, even the one in force; - one made in the edit’s own sync;
- one an
insertTextreturned; - one an
insertTextordeletewent through.
| Edit | What a moving range does |
|---|---|
| text inserted at its start | the text lands before it |
| text inserted at its end | the text lands after it |
| text inserted inside it | it grows |
| a delete | it keeps what is left of it |
Ranges in other paragraphs never move.
A range inside a deleted paragraph goes with it
Once an untracked delete() removes a paragraph, the document’s last included, its ranges go with it:
| Range | After the delete |
|---|---|
made inside it by search or getRange, in an earlier context.sync() that only read | throws ItemNotFound for the rest of the run, whether you read it, insert at it or call anything on it |
| any other range inside it | reads empty at the start of the next paragraph, and text inserted at it lands there |
| running into it from before | ends where the text before it ends, short of that text’s paragraph mark, or throws ItemNotFound when that text went too |
| ending at its start | keeps the mark before it |
| running out of it | starts at the next paragraph |
“Any other range” includes one made in the same sync as the delete or in a sync that wrote, and one insertText returned. It also includes a comment’s range, since a comment’s getRange() is itself a write.
A search made after the delete finds the document as it is. A tracked delete() keeps the paragraph and its ranges.
A range keeps its offsets when its text shrinks
With tracking off, a range a search or getRange made in an earlier context.sync() that only read keeps its offsets when an untracked delete shortens or empties its paragraph’s text. It reads no further than the text’s end. So a search’s hit in a paragraph whose whole text you deleted reads empty text.
Text inserted at a range past the text, at any location, lands at the text’s end. The range insertText returns keeps offsets past the text too, so it reads empty. insertOoxml instead returns the range its content landed in.
An end past the paragraph mark, as getRange('Whole') has, stays past it as the text grows or shrinks. Text inserted at that range’s End lands before the mark.