Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save selection text and show it later in html and javascript

I have a difficult situation with html and javascript. My html page allows user to select text and highlight it with colors. Now I want to save the state into database to show it later for that user. Of course, I can save whole html after user edited it. But I just ONLY want to save some parameters, combine with original html to show the page in the state user see last time. We can use this function:

var index = innerHTML.indexOf(text);

to highlight text at that index. But in case there are many same texts in the page, I want to highlight exactly word user highlighted it before.

Anyone can instruct me how to accomplish this with javascript?

I appreciate your help a lot.

like image 723
user2804659 Avatar asked Sep 22 '13 17:09

user2804659


People also ask

How do I save a selection in Javascript?

mousedown() and mouseup() events in javascript when selection start use mousedown() and when it endup the selection mouseup() and save the selected text in array!

How to prevent user from selecting text in CSS?

You can use the user-select property to disable text selection of an element. In web browsers, if you double-click on some text it will be selected/highlighted. This property can be used to prevent this.


1 Answers

Range objects and document.execCommand allow to manipulate selection pretty easily. The main problem in your case is saving the range object in a text format.

Basically what you need is to get the startContainer, startOffset, endContainer and endOffset, which are the values needed to create Range objects. Offsets are number so it's pretty straightforward. Containers are Nodes, which you can't directly save as strings, so that's the main problem. One thing you can do is add keys to your DOM and save the key. But then, since in ranges containers are text nodes, you'll need to save the index of the text node. Something like this should allow to tag the DOM with keys, using a recursive function:

function addKey(element) {
  if (element.children.length > 0) {
    Array.prototype.forEach.call(element.children, function(each, i) {
      each.dataset.key = key++;
      addKey(each)
    });
  }
};

addKey(document.body);

Once this is done, you can convert range objects to an object that you can save as a string. Like this:

function rangeToObj(range) {
  return {
    startKey: range.startContainer.parentNode.dataset.key,
    startTextIndex: Array.prototype.indexOf.call(range.startContainer.parentNode.childNodes, range.startContainer),
    endKey: range.endContainer.parentNode.dataset.key,
    endTextIndex: Array.prototype.indexOf.call(range.endContainer.parentNode.childNodes, range.endContainer),
    startOffset: range.startOffset,
    endOffset: range.endOffset
  }
}

Using this, you can save each selection that the user creates to an array. Like this:

document.getElementById('textToSelect').addEventListener('mouseup', function(e) {
  if (confirm('highlight?')) {
    var range = document.getSelection().getRangeAt(0);
    selectArray.push(rangeToObj(range));
    document.execCommand('hiliteColor', false, 'yellow')
  }
});

To save the highlights, you save each object to JSON. To test this, you can just get the JSON string from your range objects array. Like this (this is using the get Seletion button at the top):

document.getElementById('getSelectionString').addEventListener('click', function() {
  alert('Copy string to save selections: ' + JSON.stringify(selectArray));
});

Then when loading the empty HTML, you can use a reverse function that will create ranges from the objects you saved in JSON. Like this:

function objToRange(rangeStr) {
  range = document.createRange();
  range.setStart(document.querySelector('[data-key="' + rangeStr.startKey + '"]').childNodes[rangeStr.startTextIndex], rangeStr.startOffset);
  range.setEnd(document.querySelector('[data-key="' + rangeStr.endKey + '"]').childNodes[rangeStr.endTextIndex], rangeStr.endOffset);
  return range;
}

So you could have an array of ranges in strings that you convert to objects, and then convert to Range objects that you can add. Then using execCommand, you set some formatting. Like this (this is using the set selection button at the top, you do this after refreshing the fiddle):

document.getElementById('setSelection').addEventListener('click', function() {
  var selStr = prompt('Paste string');
  var selArr = JSON.parse(selStr);
  var sel = getSelection();
  selArr.forEach(function(each) {
    sel.removeAllRanges();
    sel.addRange(objToRange(each));
    document.execCommand('hiliteColor', false, 'yellow')
  })
});

See: https://jsfiddle.net/sek4tr2f/3/

Note that there are cases where this won't work, main problematic case is when user selects content in already highlighted content. These cases can be handled, but you'll need more conditions.

like image 195
Julien Grégoire Avatar answered Sep 29 '22 01:09

Julien Grégoire