Search code examples
javascripthtmlangularjshighlightrangy

Using any other element than document while creating the highlighter in rangy


I am using the highlighter module of rangy to highlight a certain portion of the HTML page. A specific div is going to be using the module only which is actually an angular directive. I am persisting the highlight range in the backend and rendering it again when the page gets loaded again. This is because I want to persist the highlights. The problem I am facing is that the page has few dynamic components which may or may not change on every page load. This creates problems in rendering the saved highlights. To resolve this, I tried to use the static element while creating the highlighter, using the following code

var highlighter = rangy.createHighlighter(element);

This gave me the following error -

TypeError: Failed to execute 'setStart' on 'Range': parameter 1 is not of type 'Node'.
    at WrappedRange.api.createCoreModule.rangeProto.setStart (allPluginJsPartTwo.js:42934)
    at WrappedRange.moveToBookmark (allPluginJsPartTwo.js:42427)
    at Object.characterRangeToRange (allPluginJsPartTwo.js:45712)
    at Highlight.getRange (allPluginJsPartTwo.js:45816)
    at Highlight.apply (allPluginJsPartTwo.js:45837)
    at Highlighter.deserialize (allPluginJsPartTwo.js:46203)
    at allCommonJs.js:11098
    at processQueue (allFrameworkJs.js:14804)
    at allFrameworkJs.js:14820
    at Scope.$eval (allFrameworkJs.js:16064)

(Please ignore the JS file names and code lines. They are merged by grunt.) The error comes, because a particular containerNode is undefined. The containerNode which is basically missing from here is defined as element.body which is undefined for any DOM element except document element.
I tried using the following silly workaround.

element.body = document.body;

which was just doing the exact same stuff as sending document object in createHighlighter().

So I am assuming that the rangy.createHighlighter() needs to have only document object as the parameter. My question is, how can I make it work for any element, not just the document object?


Solution

  • The exact requirement is not supported by Rangy, however, I had to create a separate function which was not that complex and I was not sure why it was not inbuilt with Rangy.

    But anyway, here's how I did it. Rangy uses character ranges to serialize and deserialize the highlighted content. So I normalized the ranges before saving them till the div which contained my concerned static HTML.

    For example, if the div had an id of page-container, I got the character range till that and subtracted that part from the range of the highlight before saving and when I wanted to deserialise the highlight, I just calculated the offset again and added it back to the normalized range and it worked like magic :-D

    Here's the code which calculated the offset -

    function getRangeOffset(){
        var converter = highlighter.converter;
        var pageContainer = document.getElementById('page-container');
        var containerRange = rangy.createRange(document);
        containerRange.setStart(pageContainer,0);
        var containerCharRange = converter.rangeToCharacterRange(containerRange);
        var rangeOffset = containerCharRange.start;
        return parseInt(rangeOffset);
    }