Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
453 views
in Technique[技术] by (71.8m points)

jquery - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...