I have a rather complex Vue component which involves a contenteditable div. I'd like to highlight words in this div using Rangy and add additional markup and keep this markup even when the text is edited.
Originally, I was going to post a question because at some point dealing with additional markup made the contenteditable div uneditable, I just could not delete or add characters. But when I tried setting up a code snippet, I got another error message.
I expect three things to happen when editing the contenteditable div:
In the
storeIndexesmethod, I create and store ranges for each element in thehighlightsarray. This method is called@beforeinput. This event is not available in all browsers, I'm using Chrome.Next, I expect the text inside the
contenteditablediv to be updated.Finally, the ranges should be restored by the
restoreIndexesmethod which is called@input.
I'm aware my code should not have any visible effect. My problem is that there's an error message when trying to edit the text: Rangy warning: Module SaveRestore: Marker element has been removed. Cannot restore selection.
What's wrong here?
new Vue({
el: '#app',
data: {
currentHighlights: [],
highlights: [
{
start: 10,
end: 20
}
],
},
methods: {
// What happens just before an edit is applied
storeIndexes: function(event) {
// Create a new range object
let range = rangy.createRange();
// Get contenteditable element
let container = document.getElementById('text-with-highlights');
// Store all currently highlights and addd DOM markers
this.highlights.forEach(highlight => {
// Move range based on character indexes
range.selectCharacters(container, highlight.start, highlight.end);
// Set DOM markers and store range
this.currentHighlights.push(rangy.saveRange(range))
});
},
// What happens after an edit was made
restoreIndexes: function(event) {
// Create a new range object
let range = rangy.createRange();
// Get range based on character indexes
let container = document.getElementById('text-with-highlights');
this.currentHighlights.forEach(highlight => {
range.selectCharacters(container, highlight.start, highlight.end);
rangy.restoreRange(range);
});
this.currentHighlights = [];
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-selectionsaverestore.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='app'>
<div @beforeinput='storeIndexes' @input='restoreIndexes' contenteditable id='text-with-highlights'>
Just some text to show the problem.
</div>
</div>
Turns out this was not a Vue problem, but rather one of code running asynchronously:
storeIndexeswas not finished whenrestoreIndexesattempted to restore ranges.setTimeoutdid the trick. I'm not sure if there's any better way than delaying the method by some random interval,However, I could get rid of my
storeIndexesmethod completely using the v-runtime-template library. This is an alternative tov-htmlbut also works for programmatically inserted elements such as the highlights in my problem.Now my highlights simply react on changing indexes in
$dataand I don't need to move them manually when thecontenteditablediv is updated.