Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use javascript to extend a DOM Range to cover partially selected nodes

Tags:

javascript

dom

I'm working on a rich text editor like web application, basically a XML editor written in javascript.

My javascript code needs to wrap a selection of nodes from the contentEditable div container. I'm using the methods described at MDC. But since I need to synchronize the div containers content to my XML DOM I would like to avoid partial selections as described in w3c ranges:

<BODY><H1>Title</H1><P>Blah xyz.</P></BODY

............^----------------^............

This selection starts inside H1 and ends inside P, I'd like it to include H1,P completely.

Is there an easy way to extend the selection to cover partially selected children completely? Basically I want to use range.surroundContents() without running into an exception.

(The code doesn't need to work with opera/IE)

like image 671
ko-dos Avatar asked Mar 19 '10 12:03

ko-dos


2 Answers

Looking at the MDC documentation, I manage do something like this:

Selection.prototype.coverAll = function() {
    var ranges = [];
    for(var i=0; i<this.rangeCount; i++) {
        var range = this.getRangeAt(i);
        while(range.startContainer.nodeType == 3
              || range.startContainer.childNodes.length == 1)
            range.setStartBefore(range.startContainer);
        while(range.endContainer.nodeType == 3
              || range.endContainer.childNodes.length == 1)
            range.setEndAfter(range.endContainer);
        ranges.push(range);
    }
    this.removeAllRanges();
    for(var i=0; i<ranges.length; i++) {
        this.addRange(ranges[i]);
    }
    return;
};

You can try it here : http://jsfiddle.net/GFuX6/9/

edit: Updated to have the browser display correctly the augmented selection. It does what you asked for, even if the selection contains several ranges (with Ctrl).

To make several partial nodes Bold, here is a solution:

Selection.prototype.boldinize = function() {
    this.coverAll();
    for(var i=0; i<this.rangeCount; i++) {
        var range = this.getRangeAt(i);
        var parent = range.commonAncestorContainer;
        var b = document.createElement('b');
        if(parent.nodeType == 3) {
            range.surroundContents(b);
        } else {
            var content = range.extractContents();
            b.appendChild(content);
            range.insertNode(b);
        }
    }
};
like image 195
Alsciende Avatar answered Nov 15 '22 13:11

Alsciende


Thanks to Alsciende I finally came up with the code at http://jsfiddle.net/wesUV/21/. This method isn't as greedy as the other one. After coverAll(), surroundContents() should always work.

Selection.prototype.coverAll = function() {
  var ranges = [];   
  for(var i=0; i<this.rangeCount; i++) {
    var range = this.getRangeAt(i);
    var ancestor = range.commonAncestorContainer;
    if (ancestor.nodeType == 1) {            
        if (range.startContainer.parentNode != ancestor && this.containsNode(range.startContainer.parentNode, true)) {
            range.setStartBefore(range.startContainer.parentNode);
        }
        if (range.endContainer.parentNode != ancestor && this.containsNode(range.endContainer.parentNode, true)) {
                range.setEndAfter(range.endContainer.parentNode);
        }
    }
    ranges.push(range);
  }
  this.removeAllRanges();
  for(var i=0; i<ranges.length; i++) {
    this.addRange(ranges[i]);
  }
  return;
};

And the boldinize function:

Selection.prototype.boldinize = function() {
  for(var i=0; i<this.rangeCount; i++) {        
    var range = this.getRangeAt(i);
    var b = document.createElement('b');
    try {
        range.surroundContents(b);
    } catch (e) {
        alert(e);
    }
  }
};
like image 32
ko-dos Avatar answered Nov 15 '22 14:11

ko-dos