0

I am currently developing an add-in that utilises text to speech to speak each sentence within the document. I use tracked objects to track the sentences as they need to be spoken one after another.

The issue arises when trying to change the font colour of the sentence (highlight whilst being spoken) by loading and accessing the 'font.color' property. This will work on Desktop, but will throw the following error Online:

Debug info: {"code":"GeneralException","message":"Cannot read property 'gO' of null","errorLocation":"Range._onAccess"}

Below is the minimum code to reproduce the issue:

...
var sentences;
...

Word.run(function (context) {
    var selectedSentence = context.document.getSelection().getTextRanges([".", "!", "?"]);

    context.load(selectedSentence)

    return context.sync().then(function () {

        sentences = selectedSentence.items[0].getRange()
        .expandTo(context.document.body.paragraphs.getLast().getRange("end"))
        .getTextRanges([".", "!", "?"]);

        context.load(sentences);
        context.trackedObjects.add(sentences);

        return context.sync(sentences);
    })
}).then(function (sentences) {

    sentences.context.load(sentences, 'font');
    return sentences.context.sync().then(function () {
        sentences.items[0].font.color = "#2E86C1";
    })
    .then(sentences.context.sync)

}).catch(errorHandler);

The error will also be produced if you simply try to access it directly:

sentences.items[0].font.color = "#2E86C1";
sentences.context.sync();
BoyUnderTheMoon
  • 741
  • 1
  • 11
  • 27

2 Answers2

1

When code works on desktop but fails with Office Online, especially if the error is about null objects, the problem is usually a broken promise chain. On the desktop, the round trip between the add-in and the Word host is local; so asnyc methods complete very quickly. In that case, they may all finish in the desired order even if there is a broken promise chain. But with Office Online, the methods take longer to run and if the chain is broken then some later methods may finish before earlier methods have completed.

It is difficult to follow your promise chain because you are inconsistent in whether you put line breaks before ".then" and sometimes you invoke context.sync(), but other times you just reference context.sync in a then(). But I think a broken promise chain is the cause of your problem.

Also, your then(function (sentences) { ... code runs after the Word.run ends. I don't think you intend this, do you? Don't you want this code inside the Word.run?

EDIT: A Microsoft engineer has tried the following version and says that it works in Word Online when both parts are called in sequence. This suggests that the second part of your code has to be inside a Word.run although it doesn't have to be the same Word.run as the first part.

Part 1:

Word.run(function (context) {
        var selectedSentence = context.document.getSelection().getTextRanges([".", "!", "?"]);
        context.load(selectedSentence);

        return context.sync().then(function () {
            sentences = selectedSentence.items[0].getRange()
            .expandTo(context.document.body.paragraphs.getLast().getRange("end"))
            .getTextRanges([".", "!", "?"]);

            context.load(sentences);
            context.trackedObjects.add(sentences);

            return context.sync(sentences);
        })
    });

Part 2:

Word.run(sentences, function(ctx){
        sentences.load('font');
        return ctx.sync().then(function(){
            sentences.items[0].font.color = "#2E86C1";
            return ctx.sync();
        });
    });
Rick Kirkham
  • 9,038
  • 1
  • 14
  • 32
  • It's intentional it is outside a Word.Run, as In the use case I expect the execution of 'Word.Run' to be complete before the next sentence is spoken (and accessing it's font property), hence why I use tracked objects so I can use 'sentences' outside it. The above code only shows a single sentence being utilised, but I actually use a speech.onend callback to iterate through each sentence. – BoyUnderTheMoon Jul 21 '17 at 09:16
  • @Daniel See my EDIT with code from a Microsoft engineer. – Rick Kirkham Jul 21 '17 at 17:00
0

I think you can solve your scenario without tracking objects, its really not needed and this is just about traversing a collection of ranges using promises. Check out this alternative: (I am adding a timer that you can use to adjust how much time you want to highlight each sentence)

function highlightSentences() {
    Word.run(function (context) {
        var myPars = context.document.body.paragraphs;
        context.load(myPars);
        return context.sync()
            .then(function () {
                var myWords = myPars.items[0].split([".","!","?"], true /*used to trim delimiters*/, true /* used to trim spaces */);
                context.load(myWords, { expand: 'font' });
                return context.sync()
                    .then(function () {
                        return forEach(myWords, function (item, i) {
                            if (i >= 1) {
                                myWords.items[i - 1].font.highlightColor = "#FFFFFF";
                            }
                            myWords.items[i].font.highlightColor = "#FFFF00";
                            return createTimerPromise(1000).then(context.sync);
                        })
                    })
            })
    })
        .catch(OfficeHelpers.Utilities.log);

    function createTimerPromise(ms) {
        return new OfficeExtension.Promise(function (resolve) {
            setTimeout(resolve, ms);
        })
    }

    function forEach(collection, handler) {
        var promise = new OfficeExtension.Promise(function (resolve) { resolve(); });
        collection.items.forEach(function (item, index) {
            promise = promise.then(function () {
                return handler(item, index);
            })
        });
        return promise;
    }
}
Juan Balmori
  • 4,898
  • 1
  • 8
  • 17