javascriptjqueryhtml-tablerangy

Insert Table into content editable div


I have a fiddle showing what my code is doing. Using javascript/jquery I am trying to insert a table into a content editable div at the current caret position. I am using Tim Down's Rangy library to accomplish this. I am doing this with the following javascript.

var range = getFirstRange();
var el = document.createElement("table");
var tableHtml = "";
for (var a = 0; a <= tableY; a++) {
    if(a%2==0){
       tableHtml += '<tr class="zebra">';
    }
    else{
       tableHtml += '<tr>';
    }
    for (var b = 0; b <= tableX; b++) {
       tableHtml += '<td>&nbsp;</td>';
    }
    tableHtml += '</tr>';   
}
$(el).html(tableHtml); 
range.insertNode(el);
rangy.getSelection().setSingleRange(range);

Just in case it helps here is the getFirstRange function.

function getFirstRange() {
   var sel = rangy.getSelection();
   return sel.rangeCount ? sel.getRangeAt(0) : null;
} 

I need to make valid html wherever this table is placed. for example if the caret is in the middle of a link I am trying to avoid the following html.

<p>some text <a href="#">text 
                         <table>
                             <tr>
                               <td>table content</td>
                             </tr>
                         </table> 
              text</a> more text</p> 

I would like it to look like this instead.

<p>some text <a href="#">text</a></p>
<table>
   <tr>
     <td>table content</td>
   </tr>
</table>
<p><a href="#">text</a> more text</p>

Solution

  • If you want to drop the new node immediately after a selected node(s) that cannot validly contain it, replace this:

    range.insertNode(el);
    

    With something like this:

    var badNodes = {a: 1, p: 1};
    
    // starting with the node at the beginning of the range,
    // iterate to the "left" until we find a node that isn't
    // a text node
    var n = range.startContainer;
    var tag = n.nodeName;
    while (tag == '#text') {
        n = n.parentNode;
        tag = n.nodeName;
    }
    
    // if the node we landed on isn't one of our bad nodes ...
    if (badNodes[tag.toLowerCase()]) {
    
        // that we refuse to insert 'el' into, continue iterating to the
        // "left" until we find a node we're willing to place 'el' after.
        while (badNodes[n.parentNode.nodeName.toLowerCase()]) {
            n = n.parentNode;
            tag = n.nodeName;
        }
        n.parentNode.insertBefore(el, n.nextSibling);
    
    } else {
        range.insertNode(el);
    }
    

    See my fiddle fork: http://jsfiddle.net/zntwL/29/


    UPDATE (I think this is what you want)

    If you want to split the invalid node(s) and drop the new node in, use something like this instead:

    var badNodes = {a: 1, p: 1};
    
    // starting with the node at the beginning of the range,
    // iterate to the "left" until we find a node that isn't
    // a text node
    var n = range.startContainer;
    var tag = n.nodeName;
    while (tag == '#text') {
        n = n.parentNode;
        tag = n.nodeName;
    }
    
    // if the node we landed on is one of our "bad" nodes ...
    if (badNodes[tag.toLowerCase()]) {
    
        // continue iterating to the "left" until we find a "good" node
        while (badNodes[n.parentNode.nodeName.toLowerCase()]) {
            n = n.parentNode;
            tag = n.nodeName;
        }
    
        // remove everything from our "good" node from the start of the
        // range to the end of the node. this causes all bad nodes to be
        // severed and auto-closed and auto-opened as necessary at the cut.
        range.setEndAfter(n);
        var clipped = range.extractContents();
    
        // drop 'el' in after the break (right where we want it)
        n.parentNode.insertBefore(el, n.nextSibling);
    
        // and re-attach the clipped portion of the "good" node, which
        // includes the auto-opened "bad" nodes.
        el.parentNode.insertBefore(clipped, el.nextSibling);
    
    } else {
        range.insertNode(el);
    }
    

    http://jsfiddle.net/zntwL/31/

    Your final solution may need some tweaking. You may need to detect #text nodes differently to be cross-browser compliant. And you'll want to modularize this and populate the badNodes array appropriately. But, I think that's the general idea.