Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing HTML then adding a document fragment back into the html

I am trying to create a highlighter tool which works like this:

  • A user first selects a range of text.
  • They then click one of the color buttons and the background of the selected text is highlighted
  • Then they select another range of text...
  • Now when they click the button all of the html is replaced with the cached version of the html (no highlights)
  • Then the new selected text that has been highlighted is appended back into the fresh html.

This way only one range of text can be highlighted at a time.

Problem:

I am having a hard time understanding the Range, Selection and Node API's

At the moment I can't add the highlighted text back into the fresh html... I am only appending it to the document.body.

What I have so far:

https://jsfiddle.net/4mb39jd6/

(function(){

  var highlighter = {

    /**
     *
     */
    init: function(){
      this.cacheDOM();
      this.bindEvents();
    },

    /**
     *
     */
    cacheDOM: function(){
      this.$html           = $('.content').html();
      this.$content        = $('.content');
      this.$highlighter    = $('.highlighter');
    },

    /**
     *
     */
    bindEvents: function(){
      this.$highlighter.on('mousedown', this.highlightSelection.bind(this));
    },

    /**
     *
     */
    highlightSelection: function(e){

      var selection = window.getSelection();          // get selection
      var text      = selection.toString();           // get selected text
      var newNode   = document.createElement('span'); // create node

      newNode.style.backgroundColor = $(e.target).css('backgroundColor'); // set node properties
      newNode.appendChild(document.createTextNode(text));                 // append selection text to node

      var range = selection.getRangeAt(0);      // 2 - get the selected range
      range.deleteContents();                   // delete the contents
      range.insertNode(newNode);                // insert the new node with the replacement text
      documentFragment = range.cloneContents(); // clone the node

      this.$content.html(this.$html);              // refresh the content
      document.body.appendChild(documentFragment); // add the new highlighted text
    },
  };
  highlighter.init();

})();

Q:

How do I add my highlighted node... which looks like this <span style="background-color: rgb(255, 255, 131);">some random text</span> back into a fresh html document so that it is in the same position.

like image 526
Jethro Hazelhurst Avatar asked Jun 25 '26 07:06

Jethro Hazelhurst


1 Answers

If the goal is to have only one highlight at a time, I'd go for a less complicated approach that:

  • When adding a highlight,
  • Checks the html for the previous highlight,
  • Removes it when found

To do so, mark your highlight <span>s with an attribute or class (or better yet, store a reference):

newNode.classList.add("js-highlight");

Add a method to remove such an element:

clearHighlight: function() {
  var selection = document.querySelector(".js-highlight");

  if (selection) {
    selection.parentElement.replaceChild(
      document.createTextNode(selection.innerText),
      selection  
    );
  }
}

Then, before replacing your range with the highlight element, call clearHighlight.

Example: https://jsfiddle.net/2tqdLfb1/

An alternative:

I also tried another approach that respected your "cached HTML" logic, but found it to be overcomplicated. The basics of that approach:

  • Check the query-path for the parentElement of the selection
  • Store the start index and end index of the selection
  • Replace the HTML by the cached HTML string
  • Find the newly injected parent element of the selection via the stored query-path
  • Split its innerText in to 1, 2 or 3 textNodes based on the selection start and end index
  • Replace the textNode that represents the selection by your highlight <span>

An example that shows how you could store the query path for your range ancestor:

function getPathUpToSelector(selector, element, path) {
  var parent = element.parentElement;

  if (parent && !element.matches(selector)) {
    var index = Array.from(parent.children).indexOf(element) + 1;
    path.push("*:nth-child(" + index + ")");

    return getPathUpToSelector(selector, parent, path);
  }

  return [selector].concat(path).join(" > ");
}
like image 104
user3297291 Avatar answered Jun 26 '26 20:06

user3297291