Search code examples
htmldomelementcodemirrorace-editor

Get an HTML element's line number


Was wondering if there is a better way to find an elements line number in the sources code.

This is what i've go so far:

// Get the item that the user is clicking on:
var focused = doc.getSelection().anchorNode;
if(focused.nodeType == 3){ // text node
    focused = focused.parentNode;
}

// Get the entire page as a string
// NOTE: <!doctype> is not included in this!
var pageStr = doc.documentElement.outerHTML;

// Get the focused node's parent and 
// find where it begins in the page.
var parentNodeStr   = focused.outerHTML;
var parentNodeIndex = pageStr.indexOf(parentNodeStr);

// Find where the focused node begins in it's parent.
var focusedStr      = focused.outerHTML;
var focusedIndex    = parentNodeStr.indexOf(focusedStr);

// Now find where the focused node begins in the overall page.
var actualIndex     = parentNodeIndex - focusedIndex;

// Grab the text above the focused node
// and count the number of lines.
var contentAbove    = pageStr.substr(0, actualIndex);
var lineNumbers     = contentAbove.split("\n").length;

console.log("lineCount", lineNumbers);

Solution

  • Here's a better solution I've come up with, hopefully this will help someone down the road that's using Ace or CodeMirror in conjunction with contenteditable:

    Setup (for newbies)

    We can obtain where the user is selecting using:

    var sel = document.getSelection();
    

    The beginning of the selection is called the "anchor" and the end is called "focus". For example, when you select a few words of text, there is a beginning and an end of the selection.

    var anchorPoint = elementPointInCode(sel.anchorNode, sel.anchorOffset);
    var focusPoint = elementPointInCode(sel.focusNode, sel.focusOffset);
    

    Since HTML contains tags and readable text, there is an offset. For example:

    <p>abcdefgh</p>
    // ...^
    

    The offset is the index within the text node string. In our example the letter "d" is offset by 4 characters from the entry point of the &gtp&lt tag. But the offset is zero based, so the offset is actually 3.

    We get the offset using:

    var offset = sel.anchorOffset;
    // -- or --
    var offset = sel.focusOffset;
    

    ... depending on what we want, the beginning of end.

    Function

    function elementPointInCode(element, offset) {
    
        // There may or may not be an offset.
        offset =  offset || 0;
    
        var node = element;
    
        // Process first node because it'll more-than-likely be a text node.
        // And we don't want to go matching text against any of the node HTML.
        //  e.g. <a href="page.html">page 1</a>
        //      where the text "page" sould match the "page" within the <a> attributes.
    
        var strIndex;
        var str;
    
        // Bump text nodes up to parent
        if(node.nodeType == 3) {
            node = node.parentNode;
            str = node.outerHTML;
            strIndex = str.indexOf(">") + offset + 1;
        } else {
            strIndex = ;
        }
    
        // This will ultimately contain the HTML string of the root node.
        var parentNodeStr = "";
        while(node){
    
            // Get the current node's HTML.
            var str = node.outerHTML;
    
            // Preemptively snag the parent
            var parent = node.parentNode;
    
            if(parent && str){
    
                // The <html> root, we won't have a parent.
                var outer = parent.outerHTML;
    
                if(outer){
    
                    // Stash the node's HTML for post processing.
                    parentNodeStr = outer;
    
                    // Cumulatively count the offset's within each node
                    strIndex += parentNodeStr.indexOf( str );
    
                }
    
            }
    
            // Work our way up to the root
            node = parent;
    
        }
    
        // Chop the root HTML by our cumulative string index
        var str = parentNodeStr.substr(0, strIndex);
    
        var Astr = str.split("\n" );
    
        return {
            row : Astr.length,
            col : Astr.pop().length
        }
    
    };