Get Non-Contiguous Selections in Word with Office.js
A developer on a Microsoft Q&A forum recently asked: “I am building a Word Add-in where users select several paragraphs using the Ctrl key (non-contiguous selection). When I call context.document.getSelection(), I only get the very last paragraph selected. Is there a way to retrieve the entire collection of non-contiguous ranges?”
This is a common point of frustration for those transitioning from Word VBA to the modern Office.js API. In the legacy COM/VBA world, the Selection object could occasionally be coerced into handling multiple panes, but the JavaScript API handles things differently.
The Hard Truth: API Limitations
Section titled “The Hard Truth: API Limitations”As of 2024, the Office.js Word API does not support retrieving non-contiguous selections via the getSelection() method. When a user selects multiple pieces of text using Ctrl + Click, the API only recognizes the last range selected as the “active” selection.
To solve this for your users, you must use a “Marker” or “Collection” workflow.
The Recommended Workaround: The “Highlight and Extract” Method
Section titled “The Recommended Workaround: The “Highlight and Extract” Method”Since you cannot grab multiple selections at once, the most professional workaround is to ask your users to “mark” their selections (e.g., using a specific Highlight color or a temporary Character Style) and then use the body.search() or split() methods to programmatically retrieve those ranges.
The Minimal Working Example
Section titled “The Minimal Working Example”This script searches the document for all text highlighted in yellow (simulating a “multi-selection”) and processes them as an array of ranges.
Tested on Word for Microsoft 365 (Version 2402) as of 2024.
async function getMarkedSelections() { await Word.run(async (context) => { // 1. Target the document body const body = context.document.body;
// 2. Search for all instances of a specific format // (In this case, we use highlighting as a proxy for selection) const searchResults = body.search("*", { matchWildcards: true });
// Load the font property so we can check highlighting searchResults.load("font");
await context.sync();
const multiSelectedRanges = [];
// 3. Iterate through found ranges and filter for yellow highlight for (let i = 0; i < searchResults.items.length; i++) { // 'Yellow' is a common highlight, but you can use custom Styles too if (searchResults.items[i].font.highlightColor === "Yellow") { multiSelectedRanges.push(searchResults.items[i]); // Optional: Do something with each range console.log("Found text: " + searchResults.items[i].text); } }
// 4. Final Sync to apply any changes await context.sync(); });}Comparison of Methods for Handling Multiple Ranges
Section titled “Comparison of Methods for Handling Multiple Ranges”| Method | Supports Non-Contiguous? | Strategy | Best For |
|---|---|---|---|
getSelection() |
No | Returns only the most recent/last range. | Single-item actions. |
body.search() |
Yes | Search by text string or wildcards. | Finding specific keywords globally. |
| Formatting Filter | Yes | Loop through ranges and check .font properties. |
“Simulated” multi-selection (the workaround). |
Paragraph.items |
Yes | Iterate through all paragraphs in the document. | Processing every block regardless of selection. |
Step-by-Step Implementation
Section titled “Step-by-Step Implementation”- Instruct the User: In your Add-in UI, tell the user: “Highlight the sections you want to process in Yellow, then click ‘Process All’.”
- Load the Body: Use
context.document.bodyto define the scope of your search. - Search with Wildcards: Using
body.search("*", { matchWildcards: true })captures every fragment of text that can be independently formatted. - Filter by Property: Inside the loop, check the
highlightColororstyle. This creates your own “collection” of ranges that mimics a multi-select. - Clear Markers (Optional): Once your logic is complete, you can programmatically remove the highlighting to clean up the document:
range.font.highlightColor = null;
Common Traps & Troubleshooting
Section titled “Common Traps & Troubleshooting”- Performance Issues: Searching a 500-page document for every single character (
*) to check for highlighting will be slow. If the document is large, try narrowing the search scope tocontext.document.getSelection()(if they selected a large block containing the sub-selections) or use a specific Style instead of a highlight color. - The “Last Selection” Confusion: If you call
getSelection()while three things are highlighted, Word will return a singleRangeobject. If those three things were selected with Ctrl, it only returns the third one. If they were selected by dragging, it returns everything from the start of the first to the end of the last (including the unselected text in between). - Word Online vs. Desktop: The behavior of
getSelection()is consistent across platforms, but the speed ofcontext.sync()is significantly slower on Word Online. Always load only the properties you need (likefontortext) to keep the add-in snappy.
Frequently Asked Questions
Section titled “Frequently Asked Questions”What if I need to apply this to specific paragraphs only?
Instead of context.document.body.search(), you can call .search() on a specific Paragraph object or a ContentControl. This limits the “scan” to a smaller area of the document.
Can I automate the “Selection” process with a button?
Yes. Many productivity add-ins use a “Collect” button. The user selects text, clicks “Collect,” and the add-in stores that Range in an array in the task pane’s memory. Once the user has “collected” all their snippets, they click a “Process” button to handle all stored ranges at once.