views:

297

answers:

2

eg. [I've removed attributes, but it's a bit of auto-generated html]

<img class="p"/>
    <div> hello world
      <p>
        <font><font size="2">text.<img class="p"/>
        some text</font></font>
      </p>
      <img class="p"/>
      <p> <font><font size="2">more text<img class="p"/>
        another piece of text
        </font></font>
      </p><img class="p"/> some text on the end
    </div>

I need to apply some highlighting with backgrounds to all text that is between two closest [in the html code] img.p elements when hovering first of them. I have no idea how to do that. Lets say I hover the first img.p - it should highlight hello world and text. and nothing else.

And now the worst part - I need the backgrounds to disappear on mouseleave.

I need it to work with any HTML mess possible. The above is just an example and structure of the documents will differ.

[tip. Processing the whole html before binding hover and putting some spans etc. is ok as long as it doesn't change the looks of the output document]

+1  A: 

That is a totally unstructured piece of HTML, which is something you should always avoid. However, you add some data to the img you want to track for hovering, like this:

[...]
<img src="#" class="master" data-friends-group="group1"/>
[...]
<span class="group1">text1</span>
[...]
<span class="group1">text2</span>
[...]

You can now catch from the the "data-friends-group" attribute the class in common to all the elements you need to highlight. Now the rest is easy stuff.

$(document).ready(function() {
    $("img.master").each(function() {
        $friends = $("." + $(this).attr("data-friends-group"));
        $(this).hover(
            function(){
                $friends.addClass("highlighted");
            },
            function(){
                $friends.removeClass("highlighted");
            }
        );
    });
});

Obviously, the class .hightlighted will be the one with the background-color: yellow;

Stefano Verna
The html comes from an application that treats it like text and splits. I can only put my own code between parts and I need to highlight these parts. I figured out a workaround based on putting some spans in the text already, but it's a lot different. +1 anyway and thanx for Your time.
naugtur
+1  A: 

Processing the whole html before binding hover and putting some spans etc. is ok

You certainly would have to do that, as you can't style text nodes, only elements.

Here's a function you could use to do it from script. (Unfortunately jQuery isn't much use here as it doesn't like handling text nodes.)

// Wrap Text nodes in a new element of given tagname, when their
// parents contain a mixture of text and element content. Ignore
// whitespace nodes.
//
function wrapMixedContentText(el, tag) {
    var elementcontent= false;
    for (var i= el.childNodes.length; i-->0;) {
        var child= el.childNodes[i];
        if (child.nodeType===1) {
            elementcontent= true;
            wrapMixedContentText(child, tag);
        }
    }
    if (elementcontent) {
        for (var i= el.childNodes.length; i-->0;) {
            var child= el.childNodes[i];
            if (child.nodeType===3 && !child.data.match('^\\s*$')) {
                var wrap= document.createElement(tag);
                el.replaceChild(wrap, child);
                wrap.appendChild(child);
            }
        }
    }
}

And here's some functions that you could use to select nodes between other nodes. (Again, jQuery doesn't currently have a function for this.)

// Get array of outermost elements that are, in document order,
// between the two argument nodes (exclusively).
//
function getElementsBetweenTree(start, end) {
    var ancestor= getCommonAncestor(start, end);

    var before= [];
    while (start.parentNode!==ancestor) {
        var el= start;
        while (el.nextSibling)
            before.push(el= el.nextSibling);
        start= start.parentNode;
    }

    var after= [];
    while (end.parentNode!==ancestor) {
        var el= end;
        while (el.previousSibling)
            after.push(el= el.previousSibling);
        end= end.parentNode;
    }
    after.reverse();

    while ((start= start.nextSibling)!==end)
        before.push(start);
    return before.concat(after);
}

// Get the innermost element that is an ancestor of two nodes.
//
function getCommonAncestor(a, b) {
    var parents= $(a).parents().andSelf();
    while (b) {
        var ix= parents.index(b);
        if (ix!==-1)
            return b;
        b= b.parentNode;
    }
    return null;
}

Possible usage:

var outer= document.getElementById('myhighlightingimagesdiv');
wrapMixedContentText(outer, 'span');

var ps= $('#myhighlightingimagesdiv .p');
ps.each(function(pi) {
    // Go up to the next image in the list, or for the last image, up
    // to the end of the outer wrapper div. (There must be a node
    // after the div for this to work.)
    //
    var end= pi===ps.length-1? outer.nextSibling : ps[pi+1];

    var tweens= $(getElementsBetweenTree(this, end));
    $(this).hover(function() {
        tweens.addClass('highlight');
    }, function() {
        tweens.removeClass('highlight');
    });
});
bobince
That's a lot of interesting code. I worked it around in a way it's working fast enough to react on hovers by adding lots of spans while generating the output html, but I'm going to look through all Your code, because I still want to know how can I possibly get these elements. Thanx for that lot of work. I'd give You more than +1, but it won't let me ;) I think i'll make it a plugin if You're not planning to.
naugtur