ko-dos
ko-dos

Reputation: 1430

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

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)

Upvotes: 11

Views: 5397

Answers (3)

ko-dos
ko-dos

Reputation: 1430

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);
    }
  }
};

Upvotes: 1

Alsciende
Alsciende

Reputation: 26971

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);
        }
    }
};

Upvotes: 10

Robusto
Robusto

Reputation: 31883

If you mean you want to include the tags H1 and P (i.e., the valid markup), don't worry. You get that for free. If you mean you want to it to include all the content within the (partial) selection, you need to access the Selection object. Read about it on Quirksmode's introduction to Range.

Upvotes: 0

Related Questions