Richo
Richo

Reputation: 1659

Create multiple selection at the same time when using document.selection in Javascript

I've known how to use the document.selection to do the highlighting. For example

/* http://jsfiddle.net/4J2dy/ */
$("#content").on('mouseup', function() {
    highlighting();
});
var highlighting = function () {
    var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection();
    if(seleted_str != "") {
        var stringToBeHighlighted = seleted_str.getRangeAt(0);
        var span = document.createElement("span");
        span.style.cssText = "background-color: #80deea";
        span.className = "MT";
        stringToBeHighlighted.surroundContents(span);
    }
};

But there is something I don't know how to achieve. Let's say that I have four layers created with the same content at the same time. And I would like to select a sentence on the controlling layer while all the same sentence in the other three layers will be selected too.(See image below) Step1

After the selection, I would like to pop out a menu(which I can do), and get the DOM element based on which button is pressed.(See image below) Step2

Could anyone tell me how to achieve this? Or it just can't be done? I would be grateful if someone could answer for me.

Upvotes: 1

Views: 113

Answers (1)

webketje
webketje

Reputation: 11016

It's kind of possible, and I would appreciate the input of SO user Tim Down as he knows a lot about JS Range/Selections, but I'll present my partial solution already.

Instead of selecting the 4 layers, you could just store the startOffset & endOffset in an external object that is updated on mouseup. The only by-effect this has, is that the user's selection will only get the color of the span when they click a layer button.

The advantage is that you can now simply work with DOM Textnodes as opposed to ranges/ selection (more complex, to me anyway). I've chosen to find the layers with a data-layer attribute on the buttons and a corresponding id on the layers themselves. I handled the 'appending' of the 'selected span' by slicing the text content of the text nodes in the layers, like so:

layer.innerHTML = txt.slice(0, selText.start) 
     + '<span class="MT" style="background-color: #80deea">'
     + txt.slice(selText.start, selText.end) + '</span>'
     + txt.slice(selText.end, txt.length);

See it in action here. I've added a cleanSelection function so only one selection is possible at a time (the start & end counters fail because selection ranges don't take into account HTML tags, so you have to get rid of the spans).

Final notes:

  • The fiddle will not work in browsers not supporting getElementsByClassName
  • The fiddle only supports one selection at a time.
  • The fiddle does not extensively test all conditions (eg, whether the nodetype of the first child is truly a text node, etc. But it ain't hard to add that yourself)

Entire JS code as reference (also in fiddle):

// this object will hold the start & end offsets of selection value
var selText = false; 

// selText will be updated on mouseup
document.body.onmouseup = getSelText; 

// on button click, selText will be highlighted
document.body.onclick = function(e) { 
  var target = e.target || e.srcElement, range, layer, txt;

  // only do if it's a layer button & the selection is non-empty
  if (target.getAttribute('data-layer') && selText !== false) {

     // first remove previous spans, they break the startOffset & endOffset of the selection range
     cleanSelection();

     // get the clicked layer
     layer = document.getElementById(target.getAttribute('data-layer'));
     // this assumes that the first node in the layer is a textNode
     txt = layer.firstChild.nodeValue;
     // let's append the selection container now
     layer.innerHTML = txt.slice(0, selText.start) 
     + '<span class="MT" style="background-color: #80deea">'
     + txt.slice(selText.start, selText.end) + '</span>'
     + txt.slice(selText.end, txt.length);

    // ...and empty the 'real selection'
    window.getSelection().collapse(); 
    // log results to console
    console.log('From char ' + selText.start + ' to char ' + selText.end + ', in ' + layer.id);
  }

};
function getSelText () {
    var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection(), stringToBeHighlighted;
    if(seleted_str !== "") {
        stringToBeHighlighted = seleted_str.getRangeAt(0);
        selText = {
          start: stringToBeHighlighted.startOffset,
          end: stringToBeHighlighted.endOffset
        };
    } else {
        selText = false;
    }
}
function cleanSelection() {
  var getText, mtSpan = document.getElementsByClassName('MT');
  for ( var i = 0; i < mtSpan.length; i++) {
    getText = mtSpan[i].innerHTML;
    mtSpan[i].previousSibling.nodeValue = mtSpan[i].previousSibling.nodeValue + getText + mtSpan[i].nextSibling.nodeValue;
    mtSpan[i].parentNode.removeChild(mtSpan[i].nextSibling);
    mtSpan[i].parentNode.removeChild(mtSpan[i]);
  }
}

Upvotes: 1

Related Questions