views:

109

answers:

3

Is it possible with JavaScript to find a given string of text on a webpage, and then calculate its distance (in pixels) from the top of the page? If so, an example would be appreciated.

A: 

this is a fun idea. anyway, i built a mini-class in mootools that does work somewhat. I have yet to test it in a more complex page layout but the premise is, find all elements with innerHTML that may be useful, scan the text for a match, if found, clone the parent element whilst replacing the text with a span, then retrieving the span's offsets from the top / left etc. this will NOT modify the existing element's HTML and is scalable.

the result is here: http://www.jsfiddle.net/dimitar/eBPFb/

and the source is this:

var getTextOffset = new Class({
    Implements: [Options],
    options: {
        selector: "*", // all elements
        filter: "innerHTML", // only those that have this property are worth lookign at
        skip: ["link", "style", "script","head","html","meta","input","textarea","select","body"] // useless tags we don't care about
    },
    initialize: function(text, options) {
        this.setOptions(options);
        if (!text) return; // nothing to do.

        this.elements = document.getElements(this.options.selector).filter(function(el) {
            return this.options.filter in el && !this.options.skip.contains(el.get("tag"));
        }, this);

        if (!this.elements.length)
            return;

        return this.findText(text);
    },
    findText: function(text) {
        var coords = false;
        this.elements.some(function(el) {
            var eltext = el.get("html");
            if (eltext.contains(text)) {
                var c = el.getCoordinates();
                var clone = new Element("div", {
                    "class": "clone",
                    styles: c,
                    html: eltext.replace(text, "<span id='posText'>"+text+"</span>")
                }).inject(document.body, "top");
                coords = document.id("posText").getCoordinates();
                clone.destroy();
            }
        });

        return coords;
    }
});


var pleistoscene = new getTextOffset("Pleistocene");

if (pleistoscene) // found text so highlight it
new Element("div", {
    styles: $merge(pleistoscene, {
        position: "absolute",
        background: "yellow"
    }),
    opacity: .7,
    title: pleistoscene.top + "px top"
}).inject(document.body); // mouseover the yelow element to see offset (pleitoscene.top)

hope this makes sense - it's rough but ought to give you some ideas. if you use a selection for this via the mouse, it becomes much easier than the search. also, keep in mind that this searches html, hence doing a find for a very good year with markup of a very <a href=''>good</a> year will fail (you can query text properties instead).

it's using array.some() which will stop looping after the first match. if you want to find multiple instance, you need to refactor.

good luck

Dimitar Christoff
Doesn't seem to work on a real HTML page.
+1  A: 

Hi, try this.

It searches the entire body for a string, adds a class of found and alerts the coordinates for each element.

I didn't see the other post unfortunately, but this one is slightly shorter.

$(document).ready(function() {
    findText("commodo");
});
function findText(text) {
    var $matchedEl = $('body:contains(' + text + ')');
    var replacedText;
    var re = new RegExp(text, 'ig');
    $matchedEl.each(function() {
        replacedText = $(this).html().replace(re, '<span class="found">' + text + '</span>');
        $(this).html(replacedText);
    });
    $("span.found").each(function() {
        var offset = $(this).offset();
        alert( "top: " + offset.top + "\n left: " + offset.left);
    });
}

And some lipsum text to search for

<div id="wrapper">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt cursus tortor, ut mollis nulla commodo non. Quisque libero mauris, ullamcorper a porttitor nec, blandit eu sem. Vestibulum ac libero mauris, in tincidunt sem. Sed aliquet porta neque ut scelerisque. Aliquam nec aliquam ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse potenti. Duis ut molestie ante. </p>
    <p>Vestibulum ac odio id tortor interdum pharetra sit amet at nunc. Praesent pellentesque justo non massa vulputate vitae consectetur augue venenatis. Cras semper nulla tincidunt dolor sagittis sodales. Aenean malesuada eleifend enim nec accumsan. Morbi ut neque metus. Aenean erat ligula, sagittis vel scelerisque et, placerat vel diam. Nunc hendrerit quam at turpis ultrices imperdiet. Suspendisse sit amet mi sed enim ultrices consectetur. Quisque eu lobortis massa. </p>
    <p>Nulla cursus, metus ut consequat adipiscing, elit nisi semper mi, at commodo orci sapien ullamcorper lorem. Quisque scelerisque felis ut felis ultrices pellentesque. </p>
</div>
Marko
Thanks for the code, however, when I run a test in FireFox, it on a page with some more content, it it is off by around 60-80 pixels.
I assume you're measuring from the top/left coordinate. I might have a play around and see what's going on.. Or have you found a solution already?
Marko
From selected text to top.
+2  A: 

Update: made more robust.

A fun and interactive demo: See it in action, here.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"&gt;
<html>
<head>
    <title>Word Finder Fun</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style type="text/css">
        body {
            font-size:          16px;
            font-weight:        normal;
            font-family:        "Trebuchet MS", Helvetica, Tahoma, Arial, sans-serif;
            padding:            10px 5%;
            line-height:        1.5;
            min-width:          680px;
        }
        table {
            border:             1px solid black;
            border-collapse:    collapse;
            margin:             1em;
        }
        th, td {
            margin:             0px;
            padding:            0.5ex;
            border:             1px solid black;
        }
        td {
            min-width:          8em;
        }
        th {
            font-weight:        bold;
            background:         wheat;
            text-align:         right;
        }
        caption {
            margin:             0;
            font-size:          1.2em;
            font-style:         italic;
            text-align:         left;
            caption-side:       top;
        }
        form {
            float:              left;
            margin:             2.5em 5em;
        }
        label {
            font-weight:        bold;
            background:         yellow;
        }
        input {
            padding:            0.5ex;
        }
        .FoundText {
            background:         red;
            margin:             0;
            padding:            0;
        }
    </style>


    <!-- jQuery is required. -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>

    <script type="text/javascript">
        function jQueryMain ()
        {
            $("#idSearchStr").focus ();             //-- User convenience, set focus to search input.

            $("button").click (SearchPageText);     //-- On button click, search away.

            $("#idForm1").submit (SearchPageText);  //-- Intercept form submit (enter key too).
        }


        function SearchPageText (zEvent)
        {
            /*--- Kill any old spans (otherwise they might interfere with new search).
            */
            var NastyOldSpans        = $("span.FoundText");

            NastyOldSpans.each (function () {var X = $(this);  X.before (X[0].innerHTML); });
            NastyOldSpans.remove();


            var SearchStr           = $("#idSearchStr")[0].value;

            //--- Ignore empty or trivial searches.
            if (!SearchStr  ||  /^\s+$/.test (SearchStr) )
                return false;

            var zAllnodes           = $("body *");
            var iNumNodes           = zAllnodes.length;
            var zRegEx              = new RegExp ('(' + SearchStr + ')', 'ig');
            var bFoundOne           = false;


            /*--- Look for string and wrap it in a span if found.
            */
            for (J=0;  J < iNumNodes;  J++)
            {
                /*-- Get node text in a way that supports both IE and Decent browsers.
                    We do NOT want to use innerHTML here.  We do not want to mess
                    with matches in HTML tags, for now.
                */
                var zNode           = $(zAllnodes[J]);
                var sNodeTxt        = (zAllnodes[J]).innerText  ||  (zAllnodes[J]).textContent;


                if (zRegEx.test (sNodeTxt) )
                {
                    var OldStr      = (zAllnodes[J]).innerHTML;
                    var NewStr      = OldStr.replace (zRegEx, '<span class="FoundText">$1<\/span>');

                    //--- We want only "leaf" nodes (contain no html).  So, if we detect a tag, skip.
                    if (/[<>]/.test (OldStr) )
                        continue;

                    //--- SET with innerHTML, so that new span will take.
                    (zAllnodes[J]).innerHTML    = NewStr;

                    bFoundOne       = true;
                }
            }


            /*--- Grab the string's particulars and update the status table.
            */
            var iNumMatchChars      = 0;
            var iTop                = 'na';
            var iLeft               = 'na';

            if (bFoundOne)
            {
                iNumMatchChars      = SearchStr.length;
                var aStrPosition    = $("span.FoundText:first").offset ();
                iTop                = aStrPosition.top;
                iLeft               = aStrPosition.left;
            }

            var zStatusTable        = $("#idStatTable")[0];
            zStatusTable.rows[0].cells[1].innerHTML = iTop;
            zStatusTable.rows[1].cells[1].innerHTML = iLeft;
            zStatusTable.rows[2].cells[1].innerHTML = iNumMatchChars;

            return false;   //-- Stop form submit
        }


        $(document).ready (jQueryMain);
    </script>
</head>

<body>
    <form id="idForm1" method="post" action="">
    <p>
        <label for="idSearchStr">Enter Search String: </label><input type="text" id="idSearchStr"><br>
        <button type="button">Find in page</button>
    </p>
    </form>

    <table id="idStatTable" summary="Search match results">
        <caption>First Match:</caption>
        <tr>
            <th>Top:</th>
            <td></td>
        </tr>
        <tr>
            <th>Left:</th>
            <td></td>
        </tr>
        <tr>
            <th>Char cnt:</th>
            <td></td>
        </tr>
    </table>

    <p>
        I never spend much time in school but I taught ladies plenty. It's true I hire my body out
        for pay, hey hey. I've gotten burned over Cheryl Tiegs, blown up for Raquel Welch. But when
        I end up in the hay it's only hay, hey hey. I might jump an open drawbridge, or Tarzan from
        a vine. 'Cause I'm the unknown stuntman that makes Eastwood look so fine.
    </p>
    <p>
        Hey there where ya goin', not exactly knowin', who says you have to call just one place
        home. He's goin' everywhere, B.J. McKay and his best friend Bear. He just keeps on movin',
        ladies keep improvin', every day is better than the last. New dreams and better scenes, and
        best of all I don't pay property tax. Rollin' down to Dallas, who's providin' my palace, off
        to New Orleans or who knows where. Places new and ladies, too, I'm B.J. McKay and this is my
        best friend Bear.
    </p>
    <p>
        Top Cat! The most effectual Top Cat! Who's intellectual close friends get to call him T.C.,
        providing it's with dignity. Top Cat! The indisputable leader of the gang. He's the boss,
        he's a pip, he's the championship. He's the most tip top, Top Cat.
    </p>
    <p>
        This is my boss, Jonathan Hart, a self-made millionaire, he's quite a guy. This is Mrs H.,
        she's gorgeous, she's one lady who knows how to take care of herself. By the way, my name is
        Max. I take care of both of them, which ain't easy, 'cause when they met it was MURDER!
    </p>
    <p>
        Mutley, you snickering, floppy eared hound. When courage is needed, you're never around.
        Those medals you wear on your moth-eaten chest should be there for bungling at which you are
        best. So, stop that pigeon, stop that pigeon, stop that pigeon, stop that pigeon, stop that
        pigeon, stop that pigeon, stop that pigeon. Howwww! Nab him, jab him, tab him, grab him,
        stop that pigeon now.
    </p>
</body>
</html>
Brock Adams
This is quiet good in the demo. I was flattered that you provides a neat example. I'm going to test it on a basic HTML page.
@subwayxpress: Thanks. Note that I just tweaked it to ensure that HTML tags were not matched.
Brock Adams